出现原因

Promise 太麻烦了

  1. let p = new Promise((resolve, reject) => setTimeout(resolve, 1000, 3));
  2. p.then((x) => console.log(x)); // 3
  3. // 改造下
  4. async function foo() {
  5. let p = new Promise((resolve, reject) => setTimeout(resolve, 1000, 3));
  6. console.log(await p);
  7. }
  8. foo();

可以结合生成器 最后一个例子 https://www.yuque.com/yejielin/mypn47/um6a15#kFdYK

=================

async

image.png
image.png(见下await)

声明

image.png

1、普通 执行流程(同步)

image.png
image.png image.png

2、有返回值的 执行流程

1、return 不是promise对象

image.png
返回值一定是Promise,会被包裹到Promise.resolve( )中,因此可以直接接.then(res),res就是return 的结果(没有就是undefined)
image.png

2、return 实现then的对象

return 的是一个对象,并且实现了then方法,这样就会直接执行这个then方法,然后把结果传递到后面的then
image.png

3、return Promise对象

如果我们的异步函数的返回值是Promise 对象,那么后面Promise的状态会由这个Promise对象决定,是执行then还是执行catch

3、抛出异常的 执行流程

普通函数,运行到 throw 抛出异常,就会报错并终止整个程序继续运行;

而async 异步函数,运行到 throw 抛出异常,不会报错,不会终止整个程序继续运行,而是会作为Promise的reject来传递,后面可以用(必须要用)catch来捕获异常
image.png

=================

await

只能用在异步函数 async 里面

1、后面跟 成功的Promise

image.png
image.png

也可以直接写new Promise
image.png
image.png

2、后面跟普通的值

image.png
image.png

3、后面跟实现then的对象

image.png
image.png

4、后面跟失败的Promise

会直接跳出运行,把结果返回给外面(且本异步函数后面不执行了),需要用catch接
image.png

也可以通过try catch语句捕获
image.png

限制

  1. // 不允许:await 出现在了箭头函数中
  2. function foo() {
  3. const syncFn = () => {
  4. return await Promise.resolve('foo');
  5. };
  6. console.log(syncFn());
  7. }
  8. // 不允许:await 出现在了同步函数表达式中
  9. function baz() {
  10. const syncFn = function() {
  11. return await Promise.resolve('baz');
  12. };
  13. console.log(syncFn());
  14. }

停止和恢复执行

JavaScript 运行时在碰到 await 关键字时,会记录在哪里暂停执行。

等到 await 右边的值可用了,JavaScript 运行时会向消息队列中推送一个任务,这个任务会恢复异步函数的执行。

  1. async function foo() {
  2. console.log(await Promise.resolve('foo'));
  3. }
  4. async function bar() {
  5. console.log(await 'bar');
  6. }
  7. async function baz() {
  8. console.log('baz');
  9. }
  10. foo();
  11. bar();
  12. baz();
  13. // baz
  14. // bar
  15. // foo
  1. async function foo() {
  2. console.log(2);
  3. console.log(await Promise.resolve(8));
  4. console.log(9);
  5. }
  6. async function bar() {
  7. console.log(4);
  8. console.log(await 6);
  9. console.log(7);
  10. }
  11. console.log(1);
  12. foo();
  13. console.log(3);
  14. bar();
  15. console.log(5);
  16. // 1
  17. // 2
  18. // 3
  19. // 4
  20. // 5
  21. // 6
  22. // 7
  23. // 8
  24. // 9

(1) 打印 1;
(2) 调用异步函数 foo();
(3)(在 foo()中)打印 2;
(4)(在 foo()中)await 关键字暂停执行,向消息队列中添加一个期约在落定之后执行的任务;
(5) 期约立即落定,把给 await 提供值的任务添加到消息队列;
(6) foo()退出;
(7) 打印 3;
(8) 调用异步函数 bar();
(9)(在 bar()中)打印 4;
(10)(在 bar()中)await 关键字暂停执行,为立即可用的值 6 向消息队列中添加一个任务;
(11) bar()退出;
(12) 打印 5;
(13) 顶级线程执行完毕;
(14) JavaScript 运行时从消息队列中取出解决 await 期约的处理程序,并将解决的值 8 提供给它;
(15) JavaScript 运行时向消息队列中添加一个恢复执行 foo()函数的任务;
(16) JavaScript 运行时从消息队列中取出恢复执行 bar()的任务及值 6;
(17)(在 bar()中)恢复执行,await 取得值 6;
(18)(在 bar()中)打印 6;
(19)(在 bar()中)打印 7;
(20) bar()返回;
(21) 异步任务完成,JavaScript 从消息队列中取出恢复执行 foo()的任务及值 8;
(22)(在 foo()中)打印 8;
(23)(在 foo()中)打印 9;
(24) foo()返回。

顺序执行、并行执行

  1. // 定义一个延迟执行
  2. async function randomDelay(id) {
  3. // 延迟 0~1000 毫秒
  4. const delay = Math.random() * 1000;
  5. return new Promise((resolve) => setTimeout(() => {
  6. console.log(`${id} finished`);
  7. resolve();
  8. }, delay));
  9. }
  10. // 顺序执行
  11. async function foo() {
  12. const t0 = Date.now();
  13. await randomDelay(0);
  14. await randomDelay(1);
  15. await randomDelay(2);
  16. await randomDelay(3);
  17. await randomDelay(4);
  18. console.log(`${Date.now() - t0}ms elapsed`);
  19. }
  20. foo();
  21. // 0 finished
  22. // 1 finished
  23. // 2 finished
  24. // 3 finished
  25. // 4 finished
  26. // 877ms elapsed
  27. // 并行执行
  28. async function foo() {
  29. const t0 = Date.now();
  30. const promises = Array(5).fill(null).map((item, index) => randomDelay(index));
  31. for (const p of promises) {
  32. console.log(`awaited ${await p}`);
  33. }
  34. /*相当于
  35. const p0 = randomDelay(0);
  36. const p1 = randomDelay(1);
  37. const p2 = randomDelay(2);
  38. const p3 = randomDelay(3);
  39. const p4 = randomDelay(4);
  40. await p0;
  41. await p1;
  42. await p2;
  43. await p3;
  44. await p4;
  45. */
  46. console.log(`${Date.now() - t0}ms elapsed`);
  47. }
  48. foo();
  49. // 1 finished
  50. // 2 finished
  51. // 4 finished
  52. // 3 finished
  53. // 0 finished
  54. // awaited 0
  55. // awaited 1
  56. // awaited 2
  57. // awaited 3
  58. // awaited 4
  59. // 645ms elapsed
  1. async function foo() {
  2. const t0 = Date.now();
  3. const p0 = randomDelay(0);
  4. const p1 = randomDelay(1);
  5. const p2 = randomDelay(2);
  6. const p3 = randomDelay(3);
  7. const p4 = randomDelay(4);
  8. await p0;
  9. await p1;
  10. await p2;
  11. await p3;
  12. await p4;
  13. setTimeout(console.log, 0, `${Date.now() - t0}ms elapsed`);
  14. }
  15. foo();
  16. // 1 finished
  17. // 4 finished
  18. // 3 finished
  19. // 0 finished
  20. // 2 finished
  21. // 877ms elapsed

=================

性能

期约与异步函数的功能有相当程度的重叠,但它们在内存中的表示则差别很大。

期约Promise
JavaScript 引擎会在创建期约时尽可能保留完整的调用栈。
在抛出错误时,调用栈可以由运行时的错误处理逻辑获取,因而就会出现在栈追踪信息中。
当然,这意味着栈追踪信息会占用内存,从而带来一些计算和存储成本。

异步函数async/await
JavaScript 运行时可以简单地在嵌套函数中存储指向包含函数的指针,就跟对待同步函数调用栈一样。
这个指针实际上存储在内存中,可用于在出错时生成栈追踪信息。
这样就不会像之前的例子那样带来额外的消耗,因此在重视性能的应用中是可以优先考虑的。

=================

实例1:读取文件

image.png
image.png

实例2:Ajax请求

封装请求
image.png

promise then 方法

image.png

async await 方法

image.png

实例3:实现sleep()

  1. async function sleep(delay) {
  2. return new Promise((resolve) => setTimeout(resolve, delay));
  3. }
  4. async function foo() {
  5. const t0 = Date.now();
  6. await sleep(1500); // 暂停约 1500 毫秒
  7. console.log(Date.now() - t0);
  8. }
  9. foo();
  10. // 1502

案例4:连锁传递值

  1. async function addTwo(x) {return x + 2;}
  2. async function addThree(x) {return x + 3;}
  3. async function addFive(x) {return x + 5;}
  4. async function addTen(x) {
  5. for (const fn of [addTwo, addThree, addFive]) {
  6. x = await fn(x);
  7. }
  8. return x;
  9. }
  10. addTen(9).then(console.log); // 19