async/await函数

基本概念

将async和await看作命令

async/await就是generator函数的语法糖,但对generator函数有部分改进:

  • generator函数执行时返回的是一个Iterator对象,而async/await立即返回一个Promise对象,相当于能够将函数内部的多个异步操作封装成为一个Promise对象。
  • async/await内部自带执行器,也就是调用后会自动执行,而generator函数必须手动执行,或者编写执行器。
  • await可以看作内部的then语法糖。
  • async函数遇到await时会等待后面的语句(紧跟着的一个语句)执行完成才会继续往下执行。此时等待的意思函数会交出执行权,让主线程去执行其他的代码,待完成await后的语句时,才能够获取执行权继续向下执行。

async

  • 被async修饰的函数执行时会立即返回一个Promise对象。
  • 函数中的返回值会作为Promise的then方法中resolved状态下的参数。
    • 无返回值则是Promise.resolve(undefined)
    • 返回值是Promise,则就是返回的该Promise
    • 其他情况则是Promise.resolve(other)
  • 停止函数执行的情况
    • 函数内部任何抛出的错误会停止函数的执行(即并不单指Promise抛出的错误和rejected状态),并将错误对象作为then方法rejected状态下的参数,或者说会被Promise.protorype.catch捕获到。这里抛出 的意思是如果错误在内部被捕获处理,则不会停止函数执行。
    • 内部的任一rejected状态的Promise若未在该Promise中被处理或未被try-catch语句捕获,则也会停止当前函数的执行,会被async返回的Promise对象的catch捕获到。

await

  • await后应该跟一个Promise对象,若不是Promise对象,将会被Promise.resolve()转化为Promise对象,并根据是否抛出错误而进行不同的操作:
    • 若无错误抛出则立即成为resolved状态,表达式的值被做为then方法的第一个回调方法的参数。
    • 抛出错误则成为rejected状态并停止函数的执行,错误对象做为catch方法的参数。

防止async函数内部出错而终止函数剩余代码执行的方式

  1. 对await命令后的Promise对象使用catch方法捕获错误。
  2. 将这部分代码放入try-catch语句块中。

使用技巧

不相关的操作之间不应该互相影响

  若一个async函数中的多个await操作之间不存在依赖关系,则不应该让其中某些await操作等待其他的await操作的完成,应该让他们并发执行。

async function foo(){
	await p1();
	await p2();
}

  此时p1和p2之间并无任何依赖关系,但是p2只有在p1执行完成以后才会执行。

//method 1
async function foo(){
	await Promise.all(p1(), p2());
}
//method 2
async function foo(){
	let a1 = p1();
	let a2 = p2();
	await a1;
	await a2;
}

  上述便是可用的两种让相互之间无依赖的操作并发执行。

await Promise.all(…)时获取返回值

async function foo() {
  let [a, b] =  await Promise.all([p1(), p2()]);
  console.log(a, b);
}

实现原理

  理解一下阮大佬的代码我就满足了[1]

//genF是generator函数
function spawn(genF) {
//直接返回一个Promise对象
	return new Promise(function(resolve, reject) {
//获取generator的Iterator对象   
	const gen = genF();
//nextF是回调函数,根据当前Promise状态做出不同的行为   
    function step(nextF) {
//定义next,使其在try-catch语句块外可见,保存Iterator上调用next的返回值{value: promise, done: boolean}
      let next;
      try {
//捕获generator函数可能出现的异常,并使当前Promise对象成为rejected状态,将错误对象传递给当前Promise的catch方法
        next = nextF();
      } catch(e) {
        return reject(e);
      }
//判断generator是否已经执行完成,完成则将返回值传递给then方法的第一个回调函数     
      if(next.done) {
        return resolve(next.value);
      }
//若未完成,将当前yield后的表达式转化为Promise对象,转换规则见参考1
      Promise.resolve(next.value).then(function(v) {
//这里将resolved状态下返回的值作为上一个yield表达式的返回值,并继续执行下一个yield表达式,这里的行为发生在下一轮的step递归调用中
        step(function() { return gen.next(v); });
      }, function(e) {
//过程中的任意错误都会停止函数的执行,并抛出异常,注意这里的使用的是gen.throw(e),直接在generator函数中抛出了异常,而非在自动执行函数中逐层向外抛出。
        step(function() { return gen.throw(e); });
      });
    }
//step函数的首次调用,generator函数执行返回的迭代器首次执行next方法时参数没有意义,因此此时还没有上一次执行的yield表达式
	step(function() { return gen.next(undefined); });
    });
}

  async/await函数可以看做是带自动执行器的generator函数,因此spawn是一个generator函数的自动执行器,注意里面的return,这里并不是要将值返回,而是终止当前(层)函数的执行。
  虽然现在好像看懂了,估计今晚我就会忘,不要企图手动执行去验证超过三层的递归函数,否则你会知道什么叫头顶发凉,原理嘛理解就行。

tip

  forEach的回调函数使用async/await会发生非预期行为,每次遍历中的await会并发执行,原因我不太清楚,反正阮一峰大佬是这么说的,可改为for循环。
  async/await函数暂停执行,即函数会保留当前执行栈
  本文中的并发执行,并是操作系统中通常意义上的线程或进程并发,因为js执行用户代码的线程只有一个,就是主线程,这里是指多个任务之间的执行互不影响,不用等待其中某些任务状态发生改变。相互之间执行的时间间隔很短,宏观上可以看作是单核多线程不考虑同步问题的并发,且每个线程顺序执行只一次。

参考

[1] 阮一峰.ES6入门
[2] 汤小丹.计算机操作系统.第4版[M]. 2014.
[3] Promise.all结合async/await

发布了48 篇原创文章 · 获赞 3 · 访问量 5154

猜你喜欢

转载自blog.csdn.net/ydeway/article/details/100771183