Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JS-ESnext-async/await #113

Open
yaofly2012 opened this issue Feb 22, 2020 · 2 comments
Open

JS-ESnext-async/await #113

yaofly2012 opened this issue Feb 22, 2020 · 2 comments

Comments

@yaofly2012
Copy link
Owner

yaofly2012 commented Feb 22, 2020

一、async/await语法

1.1 async函数(异步函数)

  1. async修饰的函数就是异步函数
    async要放在function前面而不是在其后面,它是修饰函数的。并且任何可以是函数的地方(无论是函数声明语句还是函数表达式)都可以使用async修饰。

  2. async函数会做两件事:

  • 把函数的返回值(return,throw)隐式的包裹成Promise对象:
    • resolved的value:函数执行时return语句指定的值作为返回值的resolved时的值;
    • rejected的reason:函数执行时对外抛的异常作为该返回值rejected的reason。
  • 内部可以使用await
async function computeAnswer() {
  return 42;
}
var p = computeAnswer(); // 对象p是个Promise对象
p.then(console.log); // 42
  1. 如果函数返回值本身就是个Promise对象,则也是会包裹成Promise
function wait(delay) {
	return new Promise(resolve => {
		setTimeout(resolve, delay)
	})
}

var p = null;

async function opA() {
	return p = wait(5000);
}

;(async () => {
	var r = opA();
	console.log(r === p) // false
})();

思考:是采用new Promise(resolve => resolve())方式还是使用Promsie.resolve包裹的呢?(下面揭露)

1.2 await操作符

为什么叫await而不是wait?前者是及物动词,后者不是。

语法

await是个操作符,它表示等待一个Promise对象进入终态。

var resolvedValue = await expression
  1. await只能用在async函数里,它让async函数暂停执行,一直到Promise对象进入终态;

  2. Promise对象的终态对await行为的影响:

  • 如果该Promise对象fulfilled,则把value作为await表达式的值;
  • 如果该Promise对象rejected,则抛出异常,把reason作为异常值;
  1. 本质上await后面可以是任意表达式,await会把后面表达式值包裹为一个fulfilled的 Promise对象(即使表达式的值是Promise`对象)
var p = Promise.resolve(1);
async function func() {
    var p1 = await p;
    console.log(p1 === p);  // false
}

func();

思考await是采用new Promise(resolve => resolve())方式还是直接使用Promsie.resolve包裹后面表达式的值?

揭秘

先试试下面代码输出:

const p = Promise.resolve();

(async () => {
  await p; console.log('after:await');
})();

p.then(() => console.log('tick:a'))
 .then(() => console.log('tick:b'));

规范里应该是采用new Promise(resolve => resolve()),但是实现中有些进行了优化,即采用Promise.resolve,目前测试下来
Chrome(v81)已经优化了。但是总体各浏览器/nodejs存在实现差异。
扩展:

  1. 更快的异步函数和 Promise
  2. 令人费解的 async/await 执行顺序

1.3 串行和并行问题

首先记住同一个函数作用域的await表达式都是依次执行,只有前面的await的Promise进行终态,才会执行下一个await。如果要让多个异步操作“并行”,则需要把await放在不同的函数作用域里。
MDN上面的例子很好,要好好看看:

var parallel = async function() {
  console.log('==PARALLEL with await Promise.all==');
  
  // Start 2 "jobs" in parallel and wait for both of them to complete
  await Promise.all([
      (async()=>console.log(await resolveAfter2Seconds()))(),
      (async()=>console.log(await resolveAfter1Second()))()
  ]);
}

就是把多个await表达式包装在多个匿名的异步函数里,这样他们就不在同一个函数作用域了,就不会产生依赖关系。
总结一句话: 只把存在前后依赖的await放在同一个函数作用域里。大部分使用 async/await 困境也都是因为没弄清楚同步异步问题导致的。

二、异常处理

2.1 基础

  1. 如上面对异步函数的返回值描述的那样,async函数永远不会对外抛异常,它把内部异常转成返回值Promise的rejected的reason;
  2. 只有await会抛异常。

2.2 关于异常处理

  1. 从不用 try-catch 实现的 async/await 语法说错误处理
  2. 如何优雅地处理 Async / Await 的异常?

没有组好的方式,只有更适合的方式。
还有中写法(个人比较喜欢的方式):在调用链最外层try-catch,内部不用try-catch或内部try-catch处理后直接往外再抛。

三、为啥要使用async/await 进行异步流程管理 ?

async/await = Generator + Promise

  1. 简化异步代码Promise的书写格式;
  2. 更好的开发体验,异步栈追踪
  • 异步函数里的await碰到rejected的Promise会抛异常,而Promise方式只是触发reject回调;
  • 但注意异步函数本身对异常的处理,小心异常被异常函数吃掉了(见参考3举例)。

参考

  1. 语法知识->MDN async 函数
  2. 原理背景->「译」更快的 async 函数和 promises
  3. 知识细节->[译]await VS return VS return await
  4. 如何逃离 async/await 困境
@yaofly2012
Copy link
Owner Author

yaofly2012 commented Feb 22, 2020

await内部原理【High】待续。。。

更快的异步函数【High】待续。。。

这个练习题引发的血案
v8 更快的异步函数和 Promise

@yaofly2012
Copy link
Owner Author

yaofly2012 commented Feb 23, 2020

练习

1. 输出结果分析

var a = 0
var b = async () => {
  a = a + await 10
  console.log('2', a)
}
b()
a++
console.log('1', a)

异步函数执行会保存调用栈上下文,变量a又是个值变量。看下引用类型的:

var a = { num: 0 }
var b = async () => {
  a.num = a.num + await 10
  console.log('2', a.num)
}
b()
a.num++
console.log('1', a.num)

输出结果并没有变化,可以推断调用栈里保存的引用的变量值。
修改下调用顺序:

var a = 0
var b = async () => {
  let c = await 10
  a = a + c
  console.log('2', a) // 2 11
}
b()
a++
console.log('1', a) // 1 1 

这又是为啥呢?

执行到 await 的时候会保留 堆栈中的东西,这个时候变量a并没有使用,所以并没有保留 a = 0;当 await 结束后,再使用变量a,此时a的值经过 a++ 已经变成了 1 了。所以最后输出的是11。

可以推断await(本质是yiled)把异步函数(生成器函数)分割成一段段可单独执行的代码片段,形成的调用栈也是单独的(要深入理解协程概念了)。

2. 输出结果分析

Promise.resolve()
.then(() => {
    console.log(1);
})
.then(() => {
    console.log(2);
})

;(async function() {
    console.log('a')
    await 1;
    console.log('b')
})()

Promise.resolve()
.then(() => {
    console.log(3);
})

a1b32

3. 令人费解的 async/await 执行顺序

4. 输出结果分析

async function f3() {
  var y = await 20;
  console.log(y); // 20
}

f3();

Promise.resolve()
.then(() => {
    console.log(1)
})

5. 输出结果分析(对比问题4)

async function f3() {
  var y = await Promise.resolve(20);
  console.log(y); // 20
}

f3();

Promise.resolve()
.then(() => {
    console.log(1)
})

6. 输出结果

async function async1() {
         console.log('async1 start');
         await async2();
         console.log('async1 end');
 }
 async function async2() {
        console.log('async2');
 }

 console.log('script start'); 

 setTimeout(function () {
        console.log('setTimeout');
 }, 0);

 async1();
 
 new Promise(function (resolve) {
         console.log('promise1'); 
         resolve();
 }).then(function () {
        console.log('promise2'); 
 });
 console.log('script end');

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant