yaofly2012 / note

Personal blog
https://github.com/yaofly2012/note/issues
44 stars 5 forks source link

JS-ESnext-async/await #113

Open yaofly2012 opened 4 years ago

yaofly2012 commented 4 years ago

一、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
  3. 如果函数返回值本身就是个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`对象进入终态。
```js
var resolvedValue = await expression
  1. await只能用在async函数里,它让async函数暂停执行,一直到Promise对象进入终态;

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

    • 如果该Promise对象fulfilled,则把value作为await表达式的值;
    • 如果该Promise对象rejected,则抛出异常,把reason作为异常值;
  3. 本质上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`包裹后面表达式的值?

### 揭秘 
先试试下面代码输出:
```js
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 commented 4 years ago

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

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

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

yaofly2012 commented 4 years ago

练习

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');