异步编程简介

异步编程历史

前端异步编程经历callback、promise、generate、async/await几个阶段。

目前在简单场景使用回调,步骤比较多的场景使用promise和async/await,generate昙花一现,由于其api不易理解并且不易于使用而很少使用。

promise

简介

promise目的:异步编程解决回调地狱,让程序开发者编写的异步代码具有更好的可读性。

promise规范规定了一种异步编程解决方案的API。规范规定了promise对象的状态和then方法。

promise是这种异步编程的解决方案的具体实现。

状态特性用来让使用promise的用户可以及时通知promise任务执行结果。

then特性让使用promise的用户可以控制执行完一个任务后执行下一个任务。

(使用回调进行异步编程的话,都是用户手动控制的,使用promise的话,只需要告诉promise:“我要执行什么任务”、“我执行的任务结束了”、“然后我要做什么”)

https://www.icode9.com/content-4-365156.html

浏览器实现并未完全遵照规范

promise语法

promise对象

new Promise对象时候传入函数,函数立即执行,函数接收resolve、reject参数,调用resolve或reject时候会改变promise状态。状态改变后不会再变化。

promise状态
pending
fullfilled
rejected

未调用resolve或者reject时候处于pending状态,调用resolve后处于fullfilled状态,调用reject后处于rejected状态。如果在pending状态时候,执行任务抛出错误,则变成reject状态。

  1. 状态变化后,会执行通过then注册的回调。执行顺序和调用then方法的顺序相同。

调用then方法时候,如果状态是pending则注册回调,等到状态改变时候执行,如果状态已经改变则执行相应的回调。

  1. const p = new Promise((resolve, reject) => {
  2. resolve('test');
  3. });
  4. p.then(
  5. data => console.log(1, 'resolve', data),
  6. data => console.log(1, 'reject', data)
  7. );
  8. p.then(
  9. data => console.log(2, 'resolve', data),
  10. data => console.log(2, 'reject', data)
  11. );
  12. // 执行结果
  13. 1 "resolve" "test"
  14. 2 "resolve" "test"
  1. const p = new Promise((resolve, reject) => {
  2. throw new Error('test-error');
  3. // 由于抛出错误,promise状态已经改变为rejected,再调用resolve将不会改变promise状态
  4. resolve('test');
  5. });
  6. p.then(
  7. data => console.log(1, 'resolve', data),
  8. data => console.log(1, 'reject', data)
  9. );
  10. p.then(
  11. data => console.log(2, 'resolve', data),
  12. data => console.log(2, 'reject', data)
  13. );
  14. // 执行结果
  15. 1 "reject" Error: test-error
  16. 2 "reject" Error: test-error
  1. const p = new Promise((resolve, reject) => {
  2. setTimeout(() => {
  3. resolve('test');
  4. }, 1000);
  5. });
  6. p.then(
  7. data => console.log(1, 'resolve', data),
  8. data => console.log(1, 'reject', data)
  9. );
  10. // 执行结果
  11. 1s后打印 `1 "resolve" "test"`

promise对象的方法:then、catch、all、race

then

  1. then方法接受两个参数,onFulfilled(状态变为fullfilled的回调)和onRejected(状态变为rejected的回调)。返回一个新的promise对象,返回的promise对象的状态与then的参数(onFulfilledonRejected)和onFulfilledonRejected方法中返回的值有关。

1. then方法不传参数

如果不传参数,则then方法返回的promise和调用then的promise的状态一致。

更具体地,如果没有onFullfilled参数并且promise的状态为fullfilled,那么then方法返回的promise和调用then方法的promise状态一致;如果没有onRejected参数并且promise状态为rejected,那么then方法返回的promise和调用then方法的promise状态一致。

可以简单地理解:如果上一个promise不处理,那就下一个promise处理。

  1. var p = new Promise(resolve => {
  2. throw new Error('test');
  3. });
  4. p
  5. .then(
  6. )
  7. .then(
  8. data => console.log('resolve', data),
  9. err => console.log('reject', err)
  10. );
  11. // 执行结果
  12. reject Error: test
  1. var p = new Promise(resolve => {
  2. resolve('test');
  3. });
  4. p
  5. .then(
  6. undefined, () => {}
  7. )
  8. .then(
  9. data => console.log('resolve', data),
  10. err => console.log('reject', err)
  11. );
  12. // 执行结果
  13. resolve test

2. 回调不返回值

无论onFullfilled中还是onRejected中,不返回值(即默认返回undefined),则then返回的新promise的状态变为fullfilled,值为undefined。

  1. var p = new Promise(resolve => {
  2. resolve('test');
  3. });
  4. p
  5. .then(
  6. () => {}
  7. )
  8. .then(
  9. data => console.log('resolve', data),
  10. err => console.log('reject', err)
  11. );
  12. // 执行结果
  13. resolve undefined
  1. var p = new Promise(resolve => {
  2. throw new Error('test');
  3. });
  4. p
  5. .then(
  6. () => {},
  7. () => {}
  8. )
  9. .then(
  10. data => console.log('resolve', data),
  11. err => console.log('reject', err)
  12. );
  13. // 执行结果
  14. resolve undefined

3. 返回普通值

无论onFullfilled中还是onRejected中,返回普通值,则then返回的新promise的状态变为fullfilled,值为这个值。普通值指的是,非promise对象、非thenable对象(含有then方法的对象)。

  1. var p = new Promise(resolve => {
  2. resolve('test');
  3. });
  4. p
  5. .then(
  6. () => {return 'a'},
  7. () => {return {b: 1}}
  8. )
  9. .then(
  10. data => console.log('resolve', data),
  11. err => console.log('reject', err)
  12. );
  13. // 执行结果
  14. resolve a
  1. var p = new Promise(resolve => {
  2. throw new Error('test');
  3. });
  4. p
  5. .then(
  6. () => {return 'a'},
  7. () => {return {b: 1}}
  8. )
  9. .then(
  10. data => console.log('resolve', data),
  11. err => console.log('reject', err)
  12. )
  13. // 执行结果
  14. resolve {b: 1}

4. 返回promise

无论onFullfilled中还是onRejected中,返回一个promise对象,则以该promise的任务和状态返回新的promise。

  1. var p = new Promise(resolve => {
  2. throw new Error('test');
  3. });
  4. p
  5. .then(
  6. () => {},
  7. () => {return Promise.resolve('yes');}
  8. )
  9. .then(
  10. data => console.log('resolve', data),
  11. err => console.log('reject', err)
  12. );
  13. // 执行结果
  14. resolve yes
  1. var p = new Promise(resolve => {
  2. resolve('test');
  3. });
  4. p
  5. .then(
  6. () => {return Promise.reject('error');},
  7. () => {return {a: 1}}
  8. )
  9. .then(
  10. data => console.log('resolve', data),
  11. err => console.log('reject', err)
  12. );
  13. // 执行结果
  14. reject error

5. 返回thenable

无论onFullfilled中还是onRejected中,返回一个thenable对象,则调用该对象的then方法,该then方法接收两个参数resolvePromise和rejectPromise,如果then中调用了resolvePromise,则返回的promise状态置为fullfilled,如果then中调用了rejectPromise,或者then中抛出异常,则返回的Promise状态置为rejected,在调用resolvePromise或者rejectPromise之前,返回的promise处于pending状态。

  1. var p = new Promise((r) => {throw new Error('test')});
  2. p
  3. .then(
  4. () => ({then: function(resolvePromise, rejectPromise) {resolvePromise('resolvePromise')}}),
  5. e => ({then: function(resolvePromise, rejectPromise) {rejectPromise('rejectPromise')}})
  6. )
  7. .then(
  8. data => console.log('resolve', data),
  9. e => console.log('reject', e)
  10. );
  11. // 执行结果
  12. reject rejectPromise
  1. var p = new Promise((r) => {throw new Error('test')});
  2. p
  3. .then(
  4. () => ({then: function(resolvePromise, rejectPromise) {}}),
  5. e => ({then: function(resolvePromise, rejectPromise) {}})
  6. )
  7. .then(
  8. data => console.log('resolve', data),
  9. e => console.log('reject', e)
  10. );
  11. // 执行结果
  12. promise 处于pending状态
  1. var p = new Promise((r) => {throw new Error('test')});
  2. p
  3. .then(
  4. () => {return {then: function(resolvePromise, rejectPromise) {resolve('resolvePromise')}}},
  5. e => {return {then: function(resolvePromise, rejectPromise) {throw new Error('surprise')}}}
  6. )
  7. .then(
  8. data => console.log('resolve', data),
  9. e => {console.error('reject', e)}
  10. );
  11. // 执行结果
  12. reject Error: surprise

6. 抛出错误

无论onFullfilled中还是onRejected中,抛出错误,则以rejected为状态返回新promise。

  1. var p = new Promise(resolve => {resolve('test')});
  2. p
  3. .then(
  4. () => {throw new Error('1')},
  5. e => {return true}
  6. )
  7. .then(
  8. data => console.log('resolve', data),
  9. e => {console.error('reject', e)}
  10. );
  11. // 执行结果
  12. reject Error: 1
  1. var p = new Promise((r) => {throw new Error('test')});
  2. p
  3. .then(
  4. () => {return true},
  5. e => {throw new Error('2')}
  6. )
  7. .then(
  8. data => console.log('resolve', data),
  9. e => {console.error('reject', e)}
  10. );
  11. // 执行结果
  12. reject Error: 2

catch

catch方法和then方法的reject回调用法相同,如果这时候任务处于rejected状态,则直接执行catch,catch的参数就是reject的reason;如果任务处于pending状态,则注册catch回调,等到状态变成rejected时候再执行。【阮一峰教程示例】

Promise.prototype.catch()方法是.then(null, rejection).then(undefined, rejection)的别名,用于指定发生错误时的回调函数 ——《ES6入门教程》 阮一峰
  1. p.then((val) => console.log('fulfilled:', val))
  2. .catch((err) => console.log('rejected', err));
  3. // 等同于
  4. p.then((val) => console.log('fulfilled:', val))
  5. .then(null, (err) => console.log("rejected:", err));

all

Promise.all方法用于多个异步任务执行,当所有任务都正常完成时候,再做后面处理的场景。

Promise.all方法接收一个promise数组作为参数,返回一个promise,当参数的数组中的所有promise都resolve时候,返回的promise才会resolve;而若有一个参数的数组中的promise reject,返回的promise就会reject。

Promise.all方法返回的promise的then的第一个参数onFullfilled回调的参数也是一个数组,对应参数中的数组promise resolve的结果。

  1. const p1 = Promise.resolve(1);
  2. const p2 = new Promise(resolve => {
  3. setTimeout(() => {
  4. resolve(2);
  5. }, 1000);
  6. });
  7. Promise.all([p1, p2])
  8. .then(
  9. ([result1, result2]) => {console.log('resolve', result1, result2);}
  10. );
  11. // 执行结果
  12. resolve 1 2
  1. const p1 = Promise.reject(1);
  2. const p2 = new Promise(resolve => {
  3. setTimeout(() => {
  4. resolve(2);
  5. }, 1000);
  6. });
  7. Promise.all([p1, p2])
  8. .then(
  9. ([result1, result2]) => {console.log('resolve', result1, result2);},
  10. e => console.log('reject', e)
  11. );
  12. // 执行结果
  13. reject 1

race

Promise.race方法用于多个异步任务执行,当有其中一个任务完成或失败时候,就执行后续处理的场景。

Promise.race接收一个promise数组作为参数,返回一个新的promise。当参数数组中其中一个promise resolve或者reject,返回的promise就相应地改变状态。

  1. var p1 = Promise.reject(1);
  2. var p2 = new Promise(resolve => {
  3. setTimeout(() => {
  4. resolve(2);
  5. }, 1000);
  6. });
  7. Promise.race([p1, p2])
  8. .then(
  9. data => {console.log('resolve', data);},
  10. e => {console.log('reject', e);}
  11. );
  12. // 执行结果
  13. reject 1

allSettled

Promise.allSettled用于多个异步任务都结束(完成或者失败)时候,再执行后续任务的场景。

Promise.allSettled接收一个promise数组作为参数,返回一个promise。当参数数组中所有promise状态改变后,返回的promise变为fullfilled状态。

返回的promise的onFullfilled参数接收一个结果数组作为参数,数组对应Promise.allSettled传入的promise数组。结果数组每个元素是一个对象,格式固定:{status, value, reason},标识状态、resolve返回值、reject原因。

  1. var p1 = Promise.reject(1);
  2. var p2 = new Promise(resolve => {
  3. setTimeout(() => {
  4. resolve(2);
  5. }, 1000);
  6. });
  7. Promise.allSettled([p1, p2])
  8. .then(
  9. data => {console.log('resolve', data);},
  10. );
  11. // 执行结果
  12. resolve [{status: "rejected", reason: 1}, {status: "fulfilled", value: 2}]

常见面试题

有3个异步任务,如何让他们顺序执行?

  1. const task1 = () => {
  2. return new Promise((resolve, reject) => {
  3. resolve(1);
  4. });
  5. };
  6. const task2 = () => {
  7. return new Promise((resolve, reject) => {
  8. resolve(2);
  9. });
  10. };
  11. const task3 = () => {
  12. return new Promise((resolve, reject) => {
  13. resolve(3);
  14. });
  15. };
  16. // 顺序执行3个任务
  17. task1()
  18. .then(task2)
  19. .then(task3);

async/await

Promise虽然解决了回调地狱问题,但是缺点是有不少的样板代码,并且写代码时候还是通过then注册回调方式

async、await是语法糖,可以让开发者以写同步代码的形式写异步逻辑。

语法

如果方法中有await,方法需要加async修饰符。await后面跟一个promise。await表达式结果是promise resolve的值。

  1. const task = () => {
  2. return new Promise(resolve => {
  3. setTimeout(() => {
  4. console.log('1');
  5. resolve('2');
  6. }, 1000);
  7. });
  8. };
  9. async function test() {
  10. console.log(0);
  11. const res = await task();
  12. console.log(res);
  13. }
  14. test();
  15. // 执行结果
  16. 0
  17. 1
  18. 2

async方法返回一个promise。其resolve的值就是async方法中return的值。

  1. async function task1() {
  2. return 'test';
  3. }
  4. task1()
  5. .then(console.log);
  6. // 执行结果
  7. test

如果await后面返回的promise reject掉,需要用try catch语句捕获这个reject

  1. const task = () => {
  2. return new Promise((resolve, reject) => {
  3. setTimeout(() => {
  4. reject('test-reject');
  5. }, 1000);
  6. });
  7. };
  8. async function test() {
  9. try {
  10. const res = await task();
  11. }
  12. catch (e) {
  13. console.log('error', e);
  14. }
  15. }
  16. test();
  17. // 执行结果
  18. error test-reject

手写Promise

实现promise

实现的思路可以参考从零开始手写Promise

完整代码如下

  1. // https://zhuanlan.zhihu.com/p/144058361
  2. const PENDING = 'pending'
  3. const FULFILLED = 'fulfilled'
  4. const REJECTED = 'rejected'
  5. function Promise(executor) {
  6. var _this = this
  7. this.state = PENDING; //状态
  8. this.value = undefined; //成功结果
  9. this.reason = undefined; //失败原因
  10. this.onFulfilled = [];//成功的回调
  11. this.onRejected = []; //失败的回调
  12. function resolve(value) {
  13. if(_this.state === PENDING){
  14. _this.state = FULFILLED
  15. _this.value = value
  16. _this.onFulfilled.forEach(fn => fn(value))
  17. }
  18. }
  19. function reject(reason) {
  20. if(_this.state === PENDING){
  21. _this.state = REJECTED
  22. _this.reason = reason
  23. _this.onRejected.forEach(fn => fn(reason))
  24. }
  25. }
  26. try {
  27. executor(resolve, reject);
  28. }
  29. catch (e) {
  30. reject(e);
  31. }
  32. }
  33. Promise.defer = Promise.deferred = function () {
  34. let dfd = {};
  35. dfd.promise = new Promise((resolve, reject) => {
  36. dfd.resolve = resolve;
  37. dfd.reject = reject;
  38. });
  39. return dfd;
  40. }
  41. Promise.prototype.then = function (onFulfilled, onRejected) {
  42. //_this是promise1的实例对象
  43. var _this = this;
  44. onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
  45. onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };
  46. var promise2 = new Promise((resolve, reject) => {
  47. if (_this.state === FULFILLED) {
  48. setTimeout(() => {
  49. try {
  50. let x = onFulfilled(_this.value);
  51. resolvePromise(promise2, x, resolve, reject);
  52. }
  53. catch (error) {
  54. reject(error)
  55. }
  56. });
  57. }
  58. else if (_this.state === REJECTED) {
  59. setTimeout(()=>{
  60. try {
  61. let x = onRejected(_this.reason);
  62. resolvePromise(promise2, x ,resolve, reject);
  63. }
  64. catch (error) {
  65. reject(error);
  66. }
  67. });
  68. }
  69. else if(_this.state === PENDING) {
  70. _this.onFulfilled.push(() => {
  71. setTimeout(() => {
  72. try {
  73. let x = onFulfilled(_this.value);
  74. resolvePromise(promise2, x, resolve, reject);
  75. }
  76. catch (error) {
  77. reject(error);
  78. }
  79. });
  80. });
  81. _this.onRejected.push(() => {
  82. setTimeout(() => {
  83. try {
  84. let x = onRejected(_this.reason);
  85. resolvePromise(promise2, x ,resolve, reject);
  86. }
  87. catch (error) {
  88. reject(error);
  89. }
  90. })
  91. });
  92. }
  93. });
  94. return promise2;
  95. };
  96. function resolvePromise(promise2, x, resolve, reject){
  97. if (promise2 === x) {
  98. reject(new TypeError('Chaining cycle'))
  99. }
  100. if (x && typeof x === 'object' || typeof x === 'function') {
  101. let used;
  102. try {
  103. let then = x.then
  104. if (typeof then === 'function') {
  105. then.call(
  106. x,
  107. y => {
  108. if (used) return;
  109. used = true
  110. resolvePromise(promise2, y, resolve, reject)
  111. },
  112. r =>{
  113. if (used) return;
  114. used = true;
  115. reject(r);
  116. }
  117. );
  118. }
  119. else {
  120. if (used) return;
  121. used = true;
  122. resolve(x);
  123. }
  124. }
  125. catch(e) {
  126. if (used) return;
  127. used = true;
  128. reject(e);
  129. }
  130. }
  131. else {
  132. resolve(x);
  133. }
  134. }
  135. module.exports = Promise;

可以通过测试工具验证自己实现Promise的完整性:Promise测试工具 promises-aplus-tests,这个工具提供872个测试用例,完全通过后就能够说明实现的Promise符合规范。

promises-aplus-tests的使用方法:

  1. 安装promises-aplus-tests
  2. 导出自己实现的Promise
  3. 编写test脚本,运行脚本即可测试Promise
  1. var promisesAplusTests = require("promises-aplus-tests");
  2. var adapter = require('./promise');
  3. promisesAplusTests(adapter, function (err) {
  4. // All done; output is in the console. Or check `err` for number of failures.
  5. });

实现Promise.all、Promise.race、Promise.any、Promise.allSettled

这3个方法都接受一个promise数组作为参数,返回一个promise

它们区别是:

  • Promise.any() 有一个promise成功就算成功,全部promise失败才算失败,成功的话返回成功的结果,全部失败的话,抛出’AggregateError: All promises were rejected’。
  • Promise.all() 全部promise成功才算成功,一个promise就算失败,成功的话,返回成功的数据数组,失败的话抛出最先失败的promise的reason。
  • Promise.race() 最先的promise完成则返回,promise结果和最先完成的promise一致。
  • Promise.allSettled()等所有promise都完成返回,返回的是一个结果数组,结果反映了状态和成功信息/错误原因。
  1. function all(arr) {
  2. return new Promise((resolve, reject) => {
  3. let isComplete = false;
  4. const resolveDataList = new Array(arr.length).fill(undefined);
  5. const onFullfilled = (data, i) => {
  6. if (isComplete) {
  7. return;
  8. }
  9. resolveDataList[i] = data;
  10. if (resolveDataList.every(item => item !== undefined)) {
  11. resolve(resolveDataList);
  12. }
  13. };
  14. const onRejected = reason => {
  15. if (isComplete) {
  16. return;
  17. }
  18. isComplete = true;
  19. reject(reason);
  20. }
  21. arr.forEach((promise, index) => {
  22. promise.then(
  23. data => {onFullfilled(data, index)},
  24. onRejected
  25. );
  26. });
  27. });
  28. }
  29. function race(arr) {
  30. return new Promise((resolve, reject) => {
  31. let isComplete = false;
  32. const onFullfilled = data => {
  33. if (isComplete) {
  34. return;
  35. }
  36. isComplete = true;
  37. resolve(data);
  38. };
  39. const onRejected = reason => {
  40. if (isComplete) {
  41. return;
  42. }
  43. isComplete = true;
  44. reject(reason);
  45. };
  46. arr.forEach(promise => {
  47. promise.then(
  48. onFullfilled, onRejected
  49. );
  50. });
  51. });
  52. }
  53. function any(arr) {
  54. return new Promise((resolve, reject) => {
  55. let isComplete = false;
  56. const rejectDataList = new Array(arr.length).fill(undefined);
  57. const onFullfilled = data => {
  58. if (isComplete) {
  59. return;
  60. }
  61. isComplete = true;
  62. resolve(data);
  63. };
  64. const onRejected = (reason, i) => {
  65. if (isComplete) {
  66. return;
  67. }
  68. rejectDataList[i] = reason;
  69. if (rejectDataList.every(item => item !== undefined)) {
  70. reject('AggregateError: All promises were rejected');
  71. }
  72. }
  73. arr.forEach((promise, index) => {
  74. promise.then(
  75. onFullfilled,
  76. reason => {onRejected(reason, index);}
  77. );
  78. });
  79. });
  80. }
  81. function allSettled(list) {
  82. return new Promise((resolve, reject) => {
  83. const result = new Array(list.length).fill(undefined);
  84. const onStatusChange = () => {
  85. if (result.filter(Boolean).length === list.length) {
  86. resolve(result);
  87. }
  88. }
  89. list.forEach((promise, index) => {
  90. promise.then(
  91. data => {
  92. result[index] = {status: 'fullfilled', data};
  93. onStatusChange();
  94. },
  95. reason => {
  96. result[index] = {status: 'rejected', reason};
  97. onStatusChange();
  98. }
  99. );
  100. });
  101. });
  102. }

实现并发请求控制

  1. import axios from 'axios';
  2. function multiRequest(urls, maxNum) {
  3. return new Promise((resolve, reject) => {
  4. const requestURLS = [...urls];
  5. const result = {};
  6. let urlPool = [];
  7. const createTask = (url) => {
  8. urlPool.push(url);
  9. const onComplete = (error, res) => {
  10. if (urlPool.length === 0) {
  11. resolve(urls.map(url => result[url]));
  12. }
  13. result[url] = error || res;
  14. urlPool = urlPool.filter(value => value !== url);
  15. if (urlPool.length < maxNum && requestURLS.length) {
  16. createTask(requestURLS.pop());
  17. }
  18. };
  19. axios.get(url)
  20. .then(
  21. res => {
  22. onComplete(null, res);
  23. },
  24. error => {
  25. onComplete(error);
  26. }
  27. );
  28. };
  29. while (requestURLS.length && urlPool.length < maxNum) {
  30. createTask(requestURLS.pop());
  31. }
  32. });
  33. }

promise面试题:说出代码执行结果

说明

先来看一个字节面试的真题

  1. console.log(1);
  2. new Promise(resolve => {
  3. resolve();
  4. console.log(2);
  5. }).then(() => {
  6. console.log(3);
  7. })
  8. setTimeout(() => {
  9. console.log(4);
  10. }, 0);
  11. console.log(5);

这是一个经典的promise的代码执行结果的面试题,下面我们说明一下这类题目应该如何解答,

在前端面试中,promise的代码执行结果是常出现的一个题目。这个题目主要考察应聘者对promise的理解和对事件循环的理解。

实际上只要掌握几个简单的规则,这种题目就可以轻而易举地解答。

通常这类题目的代码中,主要会出现promise/async-await、setTimeout/setInterval。其中async-await是promise语法糖,所以逻辑是一样的,setInterval和setTimeout的回调都是放在宏任务队列中,规则相同。因此只要掌握了promise和setTimeout的执行规则,就能够解答这类题目。

规则

我们需要掌握5条规则就可以解答这类题目。这5条规则涉及两个知识点:promise和事件循环。

promise

1. new Promise时候马上执行

  1. new Promise(resolve => {
  2. console.log('test');
  3. resolve();
  4. });

上面的代码执行时候,会立刻打印”test”,所以Promise实例化时候传入的回调是同步执行的

2. resolve或者reject之后状态不再改变,但后面代码会执行

  1. new Promise(resolve => {
  2. resolve();
  3. console.log('test');
  4. reject();
  5. });

上面的Promise在回调中执行了resolve、打印”test”、reject,这3个方法都会执行,但是reject不会生效,因为执行resolve后promise状态已经变为fullfilled。

事件循环

1. promise的then中的回调放入微任务队列,setTimeout放入宏任务队列

promise.catch的回调一样,也会放入微任务队列。

2. 调用栈中代码执行完之后,先取微任务队列中的任务执行,直到微任务队列为空

3. 微任务队列为空,取宏任务队列中的一个任务开始执行,然后重复上一步,直到宏任务队列为空

可以简单描述为:一个宏任务 + 所有微任务 ->一个宏任务 + 所有微任务……,循环往复。

解题思路

根据上面的规则,我们可以总结解题的思路是

  1. 先列出调用栈、宏任务队列、微任务队列及初始状态:调用栈是当前的代码,最开始宏任务队列和微任务队列为空。
  2. 遇到new Promise,直接执行Promise的函数参数
  3. 遇到resolve/reject,改变状态
  4. 遇到promise.then/promise.catch,放入微任务队列、遇到setTimeout放入宏任务队列
  5. 调用栈执行完后,从微任务队列取任务放到调用栈中执行
  6. 微任务队列执行完后,从宏任务队列取一个放到调用栈执行,然后执行上一步,直到宏任务队列为空

下面我们通过最开始提到的题目来演示一下如何操作

范例

题目:说出下面代码执行结果:

  1. console.log(1);
  2. new Promise(resolve => {
  3. resolve();
  4. console.log(2);
  5. }).then(() => {
  6. console.log(3);
  7. })
  8. setTimeout(() => {
  9. console.log(4);
  10. }, 0);
  11. console.log(5);

解题过程

0. 初始状态

代码都在调用栈中

异步编程 promise - 图2

1. 第一步

执行当前调用栈,先打印1,然后执行new Promise,打印2,然后将.then回调放到微任务队列,将setTimeout回调放到宏任务队列,然后打印5,调用栈为空

打印1,2,5

异步编程 promise - 图3

2. 第二步

查看微任务队列,取出promise.then的回调放入调用栈中执行,执行完后调用栈为空。

打印3

异步编程 promise - 图4

3. 第三步

微任务队列为空,所以查找宏任务队列中的setTimeout回调,放入调用栈中,执行完后为空。

打印4

异步编程 promise - 图5

4. 结束

调用栈为空,执行结束

异步编程 promise - 图6

下面通过几个面试真题加深理解吧。

真题

1

说出代码执行结果

  1. const promise = new Promise((resolve,reject)=>{
  2. console.log(1);
  3. resolve();
  4. console.log(2);
  5. reject()
  6. })
  7. setTimeout(()=>{console.log(5)},0)
  8. promise.then(()=>{console.log(3)})
  9. .then(()=>{console.log(6)})
  10. .catch(()=>{console.log(7)})
  11. console.log(4)

解析

答案是1,2,4,3,6,5

首先new Promise时候打印1和2,因为new Promise时候会立即执行传入的方法

然后后面代码都是异步代码,先将setTimeout的回调加入宏任务队列,再把promise.then放入到微任务队列,然后直接执行最后一句,打印4

这样宏任务代码执行完了,接下来开始执行微任务队列中的任务,由于promise resolve,因为promise resolve之后状态不会再改变,因此不会执行到reject的对调,所以打印3和6

微任务队列为空,再到宏任务队列中查找任务,找到setTimeout回调执行,打印5

调用栈、宏任务队列、微任务队列都为空,代码执行结束。

2

说出代码执行结果

  1. const first = () => (new Promise((resolve, reject) => {
  2. console.log(3);
  3. let p = new Promise((resolve, reject) => {
  4. console.log(7);
  5. setTimeout(() => {
  6. console.log(5);
  7. resolve();
  8. }, 0);
  9. resolve(1);
  10. });
  11. resolve(2);
  12. p.then((arg) => {
  13. console.log(arg);
  14. });
  15. }));
  16. first().then((arg) => {
  17. console.log(arg);
  18. });
  19. console.log(4);

解析

3, 7, 4, 1, 2, 5

首先定义first

然后执行first,然后执行new Promise传入的方法,先打印3

又new Promise,执行其中传入的方法,打印7

执行setTimeout,将回调放入宏任务队列

执行resolve(1),将内部promise状态置为fullfilled,值为1

执行resolve(2),将外部promise状态置为fullfilled,值为2

执行内部promise.then方法,将回调加入微任务队列

执行first().then,即外部的promise,将回调加入到微任务队列

调用栈为空,开始从微任务队列拿取任务,首先拿到内部promise的回调,打印其值1

然后从微任务队列中拿取外部的promise的回调,打印其值2

此时微任务队列为空,开始从宏任务队列中拿取任务,即setTimeout回调,打印5。

调用栈,宏任务队列和微任务队列都为空,执行结束。