js-同步与异步

@


① 同步API, 异步API

同步API:同步任务都在主线程上执行,形成一个执行栈。 只有当前API执行完成后,才能继续执行下一个API

console.log('before');
console.log('after');
/*输出结果
*、

异步API:当前API的执行不会阻塞后续代码的执行
JS 的异步是通过回调函数实现的。

回调函数: 自己定义函数让别人去调用

// getData函数定义
function getData (callback) {}
// getData函数调用
getData (() => {});

一般而言,异步任务有以下三种类型:
1、事件函数, 如 click、 resize
2、资源加载, 如load、 error
3、定时器, 包括 setInterval、 setTimeout
4、ajax
异步任务相关回调函数添加到任务队列中(任务队列也称为消息队列)

console.log('before');
//异步API不会阻塞后续代码的执行
setTimeout(() => {console.log('last');}, 2000);
console.log('after');
/*
输出结果:
before 
after
last
*/


② js执行机制

JavaScript的执行环境是「单线程」
所谓单线程,是指JS引擎中负责解释和执行JavaScript代码的线程只有一个,也就是一次只能完成一项任务,这个任务执行完后才能执行下一个,它会「阻塞」其他任务。这个任务可称为主线程
异步模式可以一起执行多个任务

  1. 先执行执行栈中的同步任务。
  2. 异步任务(回调函数)放入任务队列中。
  3. 一旦执行栈中的所有同步任务执行完毕,系统就会按次序读取任务队列中的异步任务,于是被读取的异步任务结束等待状态,进入执行栈, 开始执行。

请添加图片描述

由于主线程不断的重复获得任务、执行任务、 再获取任务、 再执行, 所以这种机制被称为事件循环( event loop) 。

console.log(1);
document.onclick = function() {
console.log('click');
}
console.log(2);
setTimeout(function() {
console.log(3)
}, 3000)

请添加图片描述

控制台输出结果

1
2
//点击屏幕之后
click
//3秒之后
3
//点击屏幕之后
click

由于这种机制,使得异步与同步一起时会出现一些问题,比如:

  1. 同步API不可直接从返回值中拿到异步API执行的结果
    // 异步
    function getMsg(callback) {
        setTimeout(function() {
            callback({
                msg: 'hello node.js'
            })
        }, 2000)
    }
    let msg = null;
    getMsg(function(data) {
        //console.log(data);
        msg = data;
        console.log(msg);
    });
    console.log(msg);
    
    /*执行输出
    null
    { msg: 'hello node.js' }
    */
    
  2. 同步API从上到下依次执行,前面代码会阻塞后面代码的执行,异步API不会等待API执行完成后再向下执行代码,使用回调函数获取异步API执行结果
    在这里插入图片描述
    console.log('代码开始执行');
    setTimeout(() => {
    	console.log('2秒后执行的代码');
    }, 2000);
    setTimeout(() => {
    	console.log('"0秒"后执行的代码');
    }, 0);
    console.log('代码结束执行');
    


③ 回调地狱

有时为了保证执行顺序,不得不在异步操作的回调中继续异步操作,如此不断嵌套,便形成 回调地狱,比如:

  • ajax的异步请求
    $.ajax({
      url: 'http://localhost:3000/data',
      success: function(data) {
        console.log(data)
        $.ajax({
          url: 'http://localhost:3000/data1',
          success: function(data) {
            console.log(data)
            $.ajax({
              url: 'http://localhost:3000/data2',
              success: function(data) {
                console.log(data)
              }
            });
          }
        });
      }
    });
  • nodeJs中的文件读取

问题:如果异步API后面代码的执行依赖当前异步API的执行结果,但实际上后续代码在执行的时候异步API还没有返回结果,这个问题要怎么解决呢?

// 文件读取为异步操作
fs.readFile('./demo.txt', (err, result) => {});
console.log('文件读取结果');

需求:依次读取A文件、B文件、C文件

const fs = require('fs');

fs.readFile('./1.txt', 'utf8', (err, result1) => {
	console.log(result1)
	fs.readFile('./2.txt', 'utf8', (err, result2) => {
		console.log(result2)
		fs.readFile('./3.txt', 'utf8', (err, result3) => {
			console.log(result3)
		})
	})
});

回调地狱: 回调嵌套回调,虽然可实现上述需求,可代码可读性差,难以维护



④ Promise (ES6)

Promise 概述#

Promise 是异步编程的一种解决方案,从语法上讲,Promise是一个对象,从它可以获取异步操作的消息。
在这里插入图片描述

使用 Promise 主要有以下好处:
⚫ 可以避免多层异步调用嵌套问题(回调地狱)
⚫ Promise 对象提供了简洁的API,使得控制异步操作更加容易

Promise - MDN

Promise 基本使用#

  • 实例化 Promise 对象,构造函数中传递函数,该函数中用于处理异步任务
  • resolvereject 两个参数用于处理成功和失败两种情况,并通过 p.then 获取处理结果通过Promise把 异步API同步化
var p = new Promise(function(resolve, reject){
  // 这里用于实现异步任务
  setTimeout(function(){
    var flag = false;
    if(flag) {
      // 成功时调用
      resolve('hello');
    }else{
      // 失败时调用
      reject('出错了');
    }
  }, 100);
});
p.then(function(data){
	// 从resolve得到正常结果
  console.log(data)
},function(info){
	// 从reject得到错误信息
  console.log(info)
});

基于Promise处理Ajax请求#

关于then的链式调用,可用来处理多个 ajax 请求,比如:

 function queryData(url) {
   var p = new Promise(function(resolve, reject){
     var xhr = new XMLHttpRequest();
     xhr.onreadystatechange = function(){
       if(xhr.readyState != 4) return;
       if(xhr.readyState == 4 && xhr.status == 200) {
         // 处理正常的情况
         resolve(xhr.responseText);
       }else{
         // 处理异常情况
         reject('服务器错误');
       }
     };
     xhr.open('get', url);
     xhr.send(null);
   });
   return p;
 }
 queryData('http://localhost:3000/data')
      .then(function(data){
        console.log(data) // Hello World!
        /* 
        在then方法中,可以直接return 普通数据 或 Promise对象,
        若返回 Promise对象作为后面的then(data)的调用者, data用来接收对象中异步执行结果
        而普通数据也可以被后面的then(data) 中的 data接收
        */
        return queryData('http://localhost:3000/data1');
      })
      .then(function(data){
        console.log(data); // 输出 Hello TOM!
        return queryData('http://localhost:3000/data2');
      })
      .then(function(data){
        console.log(data) // 输出 Hello JERRY!
        return 'abc'
      })
	  .then(function(data){
		console.log(data) // 输出 abc
	  })	
;

Promise 基本API#

实例方法#

方法 描述
p.then() 得到异步任务 resolve()返回的正确结果
p.catch() 获取异步任务 reject()返回的异常信息
p.finally() 无论异步任务执行成功与否都会执行(尚且不是正式标准)

注意:p.then()既可以得到 异步的正确结果吗,也可以得到 错误结果

function foo() {
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            // resolve(123);
            reject('error');
        }, 100);
    })
}
foo()
    .then(function (data) {
        console.log(data)
    })
    .catch(function (data) {
        console.log(data)
    })
    .finally(function () {
        console.log('finished')
    });
// 等效写法    
// foo()
//     .then(function(data) {
//         console.log(data)
//     }, function(data) {
//         console.log(data)
//     })
//     .finally(function() {
//         console.log('finished')
//     });

对象方法#

方法 描述
Promise.all() 并发处理多个异步任务,所有任务都执行完成才能得到结果
Promise.race() 并发处理多个异步任务,只要有一个任务完成就能得到结果
  • Promise.all方法接受一个数组作参数,数组中的对象(p1、p2、p3)均为 promise实例(如果不是一个promise,该项会被用Promise.resolve转换为一个promise)。它的状态由这三个promise实例决定
  • Promise.race方法同样接受一个数组作参数。当p1, p2, p3中有一个实例的状态发生改变(变为fulfilledrejected),p的状态就跟着改变。并把第一个改变状态的promise的返回值,传给p的回调函数
// 调用上述的 queryData() 获取多个 包装了异步任务的promise对象
var p1 = queryData('http://localhost:3000/a1');
var p2 = queryData('http://localhost:3000/a2');
var p3 = queryData('http://localhost:3000/a3');
// 通过all()处理
Promise.all([p1,p2,p3]).then(function(result){
  console.log(result); 
  // 输出一个数组,其中的每个元素为异步任务的执行结果 (3) ['Hello TOM!', 'Hello JERRY!', 'Hello SPIKE!']
})
// 通过 race() 处理
Promise.race([p1, p2, p3]).then(function(result) {
    console.log(result) // 输出首个异步任务的执行完毕的结果: Hello TOM! 
})


⑤ 异步函数

异步函数是异步编程语法的终极解决方案,它可以让我们将异步代码写成同步的形式,让代码不再有回调函数嵌套,使代码变得清晰明了

异步函数离不开 async、await关键字, async/await是ES7引入的新语法,可以更加方便的进行异步操作

async关键字#

  1. 普通函数定义前加async关键字 普通函数变成异步函数
  2. 在异步函数内部使用return关键字进行结果返回,结果会被包裹的promise对象中, return关键字代替了resolve方法
  3. 在异步函数内部使用throw关键字抛出程序异常 , throw关键字替代了reject 方法
  4. 异步函数通过return or throw默认返回 包含返回结果的promise对象
  5. 调用异步函数再链式调用then()获取异步函数return的结果
  6. 调用异步函数再链式调用catch()获取异步函数throw的错误信息

await关键字#

  1. await关键字只能出现在异步函数中
  2. await promise ,await后面只能写promise对象 写其他类型的API是不不可以的
  3. await关键字可是暂停异步函数向下执行 ,直到promise返回结果
async function queryData() {
  var ret = await new Promise(function(resolve, reject){
    setTimeout(function(){
      resolve('nihao')
      // resolve('nihao1')
    },1000);
  })
  // console.dir(ret)
  if(ret == 'nihao'){
    return ret; //Promise { 'nihao' }
  }else {
    throw 'error!' // Promise { 'error!' }
  }
}
queryData().then(function(data){
  console.log(data)
}).catch(function(err){
  console.log(err);
})

作者:Hong•Guo

出处:https://www.cnblogs.com/ghnb1/p/15848420.html

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   Hong•Guo  阅读(259)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示