TIPS
文章转自:
# 是什么
JavaScript在设计之初便是单线程,即指程序运行时,只有一个线程存在,同一时间只能做一件事。为什么要这么设计,跟JavaScript的应用场景有关。JavaScript初期作为一门浏览器脚本语言,通常用于操作DOM,如果是多线程,一个线程进行了删除DOM,另一个添加DOM,此时浏览器该如何处理?为了解决单线程运行阻塞问题,保证用户界面的强一致性,JavaScript用到了计算机系统的一种运行机制,这种机制就叫做事件循环(Event Loop)或者消息队列(Message Queue)。
# 事件循环(Event Loop)
在JavaScript中,所有的任务都可以分为:
- 同步任务:立即执行的任务,同步任务一般会直接进入到主线程中执行。
- 异步任务:异步执行的任务,比如ajax网络请求,setTimeout定时函数等。
同步任务与异步任务的运行流程图如下:

从上面我们可以看到,同步任务进入主线程,即主执行栈,异步任务进入任务队列,主线程内的任务执行完毕为空,会去任务队列读取对应的任务,推入主线程执行。上述过程的不断重复就是事件循环。
# 宏任务与微任务
如果将任务划分为同步任务和异步任务并不是那么的准确,举个例子:
console.log(1);
setTimeout(() => {
console.log(2);
}, 0);
new Promise((resolve, reject) => {
console.log('new Promise');
resolve();
}).then(() => {
console.log('then');
});
console.log(3);
2
3
4
5
6
7
8
9
10
11
12
13
14
如果按照上面流程图来分析代码,我们会得到下面的执行步骤:
console.log(1),同步任务,主线程中执行。setTimeout(),异步任务,放到Event Table,0毫秒后console.log(2)回调推入Event Queue中。new Promise,同步任务,主线程直接执行。.then,异步任务,放到Event Table。console.log(3),同步任务,主线程执行。
所以按照分析,它的结果应该是1->'new Promise'->3->2->'then'。
但是实际结果是:1->'new Promise'->3->'then'->2。
出现分歧的原因在于异步任务执行顺序,事件队列其实是一个**“先进先出”**的数据结构,排在前面的事件会优先被主线程读取。例子中setTimeout回调事件是先进入队列中的,按理说应该先于.then中的执行,但是结果却偏偏相反。
原因在于异步任务还可以细分为微任务与宏任务,ES6规范中,**宏任务(Macrotask)**称为Task,**微任务(Microtask)**称为Jobs。宏任务是由宿主(浏览器、Node)发起的,而微任务由JavaScript自身发起。
宏任务与微任务的几种创建方式:
| 宏任务(Macrotask) | 微任务(Microtask) |
|---|---|
| setTimeout/setInterval | requestAnimationFrame(有争议) |
| UI rendering/UI事件 | MutationObserver(浏览器环境) |
| postMessage、MessageChannel | Object.observe(已废弃;Proxy 对象替代) |
| I/O,事件队列 | Promise.[ then/catch/finally ]的回调函数,await之后的代码 |
| setImmediate(Node环境) | process.nextTick(Node环境) |
| script(整体代码块,可以理解为外层同步代码) | queueMicrotask |
TIPS
如何理解script(整体代码块)是个宏任务呢?
实际上如果同时存在两个script代码块,会首先在执行第一个script代码块中的同步代码,如果这个过程中创建了微任务并进入了微任务队列,第一个script同步代码执行完之后,会首先去清空微任务队列,再去开启第二个script代码块的执行。所以这里应该就可以理解script(整体代码块)为什么会是宏任务。
requestAnimationFrame是不是微任务?
requestAnimationFrame的任务级别是很高的,拥有单独的队列维护。在浏览器1帧的周期内,requestAnimationFrame与JavaScript执行,浏览器重绘是同一个Level的。
function recursionRaf() {
requestAnimationFrame(() => {
console.log('raf回调')
recursionRaf()
})
}
recursionRaf();
2
3
4
5
6
7
无限递归时,仍然不会阻塞浏览器操作
An event loop has one or more task queues. A task queue is a set of tasks.
JavaScript是事件驱动的,所以Event Loop是异步任务调度的核心。虽然我们一直说任务队列,但是Tasks在数据结构上不是队列(Queue),而是集合(Set)。在每一轮Event Loop中,会取出第一个runnable的Task(第一个可执行的Task,并不一定是顺序上的第一个Task)进入Main Thread执行,然后再检查Microtask队列并执行队列中所有Microtask。
事件循环又叫做消息队列,是浏览器渲染主线程的工作方式。在Chrome的源码中,它开启一个不会结束的for循环,每次循环从消息队列中取出第一个任务执行,而其他线程只需要在合适的时候将任务加入到队列末尾即可。
过去把消息队列简单的分为宏任务队列和微任务队列,这种说法目前已无法满足复杂的浏览器环境,取而代之的是一种更加灵活多变的处理方式。
根据W3C的解释,每个任务有不同的类型,同类型的任务必须在同一个队列,不同的任务可以属于不同的队列。不同的任务队列有不同的优先级,在一次事件循环中,由浏览器自行决定取哪一个队列的任务。但浏览器必须有一个微队列,微队列的任务一定具有最高的优先级,必须优先调度执行。
目前Chrome的实现中,至少包含下面的队列:
- 延时队列:用于存放计时器到达后的回调任务,优先级:中。
- 交互队列:用于存放用户操作后产生的事件处理任务,优先级:高。
- 微队列:用于存放需要最快执行的任务,优先级:最高。
# 微任务
一个需要异步执行的函数,执行时机是在主函数执行结束之后、当前宏任务结束之前。在异步任务中,会先执行完所有的微任务。需要注意的是,新创建的微任务会立即进入微任务队列排队执行,不需要等待下一次事件循环。
# 宏任务
宏任务的时间粒度比较大,执行的时间间隔是不能精确控制的,对一些高实时性的需求就不太符合。
这时候,事件循环,宏任务,微任务的关系如图所示:

按照这个流程,它的执行机制是:
- 执行一个宏任务,如果遇到微任务就将它放到微任务的事件队列中。
- 当前宏任务执行完成后,会查看微任务的事件队列,然后将里面的所有微任务依次执行完。
# Task和Microtask什么时候进入相应队列
宏任务进队列
对于Task而言,任务注册时就会进入队列,只是任务的状态还不是
runnable,不具备被Event Loop捞起的条件。微任务进队列
前面我们提到一个观点,执行完一个Task后,如果Microtask队列不为空,会把Microtask队列中所有的Microtask都取出来执行。Microtask在变为
runnable状态时才进入Microtask队列。Promise状态发生转移会进入runnable状态。例如Promise的微任务不是在new Promise时进入队列的而是在.then/catch这些执行时进入的。
TIPS
setTimeout(function() {
console.log('我是setTimeout注册的宏任务')
}, 0)
2
3
使用setTimeout这行代码时,相应的宏任务就被注册了,并且Main Thread会告知定时器线程,“你定时0毫秒后给我一个消息”。定时器线程收到消息,发现只要等待0毫秒,立马就给Main Thread一个消息,“我这边已经过了0毫秒了”。Main Thread收到这个回复消息后,就把相应宏任务的状态置为runnable,这个宏任务就可以被Event Loop捞起了。
可以看到,经过这样一个线程间通信的过程,即便是延时0毫秒的定时器,其回调也并不是在真正意义上的0毫秒之后执行,因为通信过程就需要耗费时间。
HTML Living Standard: If nesting level is greater than 5, and timeout is less than 4, then set timeout to 4.
根据W3C的标准:如果timeout的嵌套超过了5层,那么timeout最少是4ms。
回到上面的题目
console.log(1);
setTimeout(() => {
console.log(2);
}, 0);
new Promise((resolve, reject) => {
console.log('new Promise');
resolve();
}).then(() => {
console.log('then');
});
console.log(3);
2
3
4
5
6
7
8
9
10
11
12
13
14
流程如下
- 遇到
console.log(1),直接打印1。 - 遇到定时器,属于新的宏任务,留着后面执行。
- 遇到
new Promise,这个是直接执行的,打印'new Promise'。 .then属于微任务,放入微任务队列,后面再执行。- 遇到
console.log(3)直接打印3。 - 好了本轮宏任务执行完毕,现在去微任务列表查看是否有微任务,发现
.then的回调,执行它,打印'then'。 - 当一次宏任务执行完,再去执行新的宏任务,这里就剩一个定时器的宏任务了,执行它,打印
2。
# async与await
async是异步的意思,await则可以理解为等待。放到一起可以理解async就是用来声明一个异步方法,而await是用来等待异步方法执行。
# async
async函数返回一个promise对象,下面两种方法是等效的:
function f() {
return Promise.resolve('TEST');
}
// asyncF is equivalent to f!
async function asyncF() {
return 'TEST';
}
2
3
4
5
6
7
8
# await
正常情况下,await命令后面是一个Promise对象,返回该对象的结果。如果不是Promise对象,就直接返回对应的值:
async function f() {
// 等同于
// return 123
return await 123;
}
f().then((v) => console.log(v)); // 123
2
3
4
5
6
7
不管await后面跟着的是什么,await都会阻塞后面的代码:
async function fn1() {
console.log(1);
await fn2();
console.log(2); // 阻塞
}
async function fn2() {
console.log('fn2');
}
fn1();
console.log(3);
2
3
4
5
6
7
8
9
10
11
12
上面的例子中,await会阻塞下面的代码(即加入微任务队列),先执行async外面的同步代码,同步代码执行完,再回到async函数中,再执行之前阻塞的代码。
所以上述输出结果为:1->fn2->3->2。
# 流程分析
通过对上面的了解,我们对JavaScript对各种场景的执行顺序有了大致的了解。
这里直接上代码:
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(() => {
console.log('settimeout');
});
async1();
new Promise((resolve) => {
console.log('promise1');
resolve();
}).then(() => {
console.log('promise2');
});
console.log('script end');
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
分析过程:
- 执行整段代码,遇到
console.log('script start')直接打印结果,输出script start。 - 遇到定时器了,它是宏任务,先放着不执行。
- 遇到
async1(),执行async1函数,先打印async1 start,下面遇到await怎么办?先执行async2,打印async2,然后阻塞下面代码(即加入微任务列表),跳出去执行同步代码。 - 跳到
new Promise这里,直接执行,打印promise1,下面遇到.then(),它是微任务,放到微任务列表等待执行。 - 最后一行直接打印
script end,现在同步代码执行完了,开始执行微任务,即await下面的代码,打印async1 end。 - 继续执行下一个微任务,即执行
then的回调,打印promise2。 - 上一个宏任务所有事都做完了,开始下一个宏任务,就是定时器,打印
settimeout。
所以最后的结果是:script start->async1 start->async2->promise1->script end->async1 end->promise2->settimeout
# Promise试题深入分析
Promise.resolve()
.then(() => {
console.log(1);
})
.then(() => {
console.log(3);
})
.then(() => {
console.log(5);
})
Promise.resolve()
.then(() => {
console.log(2);
})
.then(() => {
console.log(4);
})
.then(() => {
console.log(6);
})
// 输出:1 2 3 4 5 6
// 同步任务创建了6个微任务,但是只有1和2是 runnable
// 把这两个推到 Microtask 队列中,顺序执行,1执行完后3的状态改变,被拉起
// 2执行完后4的状态改变 同样被推送到队列中
// 此时队列中存在3 4 ,重复此步骤
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
TIPS
resolve()本质作用
resolve()是用来表示promise的状态为fulfilled,相当于只是定义了一个有状态的Promise,但是并没有调用它。promise调用then的前提是promise的状态为fulfilled。- 只有
promise调用then的时候,then里面的函数才会被推入微任务中。
Promise.resolve()
.then(() => {
// mt1
console.log(0);
return Promise.resolve(4);
}).then((res) => {
// mt2
console.log(res);
});
Promise.resolve()
.then(() => {
// mt3
console.log(1);
}).then(() => {
// mt4
console.log(2);
}).then(() => {
// mt5
console.log(3);
})
.then(() => {
// mt6
console.log(5);
})
.then(() => {
// mt7
console.log(6);
});
// 输出:0 1 2 3 4 5 6
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
这里4怎么跑到3后面去了?
在我看来,这道题有两个Promise.resolve(),相当于创建两个状态为fulfilled的Promise,紧随他们后面的第一个then方法会交替将其执行函数送入微任务队列排队执行,所以这里的0和1,大家都可以理解,但是接下来执行的不是console.log(res)而是console.log(2)。如果说需要等待return Promise.resolve(4)执行完并将其结果和状态同步给外部的Promise,那么这里只需要创建一个微任务去处理就应该可以了,也就是4会在2后面才对,为啥需要创建两个微任务呢?
步骤分析:
- 同步任务执行,注册mt1~mt7七个微任务,此时
execution context stack为空,并且mt1和mt3的状态变为runnable。JS引擎安排mt1和mt3进入Microtask队列(通过HostEnqueuePromiseJob实现)。 Perform a microtask checkpoint,由于mt1和mt3是在同一次JS call中变为runnable的,所以mt1和mt3的回调先后进入execution context stack执行。- mt1回调进入
execution context stack执行,输出0,返回Promise.resolve(4)。mt1出队列。由于mt1回调返回的是一个状态为fulfilled的Promise,所以之后JS引擎会安排一个job(job是ecma中的概念,等同于微任务的概念,这里先给它编号mt8),其回调目的是让mt2的状态变为fulfilled(前提是当前execution context stack is empty)。所以紧接着还是先执行mt3的回调。 - mt3回调进入
execution context stack执行,输出1,mt4变为runnable状态,execution context stack is empty,mt3出队列。 - 由于此时mt4已经是
runnable状态,JS引擎安排mt4进队列,接着JS引擎会安排mt8进队列。 - 接着,mt4回调进入
execution context stack执行,输出2,mt5变为runnable,mt4出队列。JS引擎安排mt5进入Microtask队列。 - mt8回调执行,目的是让mt2变成
runnable状态,mt8出队列。mt2进队列。 - mt5回调执行,输出3,mt6变为
runnable,mt5出队列。mt6进队列。 - mt2回调执行,输出4,mt2出队列。
- mt6回调执行,输出5,mt7变为
runnable,mt6出队列。mt7进队列。 - mt7回调执行,输出6,mt7出队列。执行完毕!总体来看,输出结果依次为:
0->1->2->3->4->5->6。
Promise.resolve()与new Promise(resolve => resolve())的区别:
如果参数是promise实例,
new Promise(resolve => resolve())返回新promise实例,Promise.resolve()原样返回。参数是一个thenable对象, 都返回新promise实例。
thenable = { then: function(resolve, reject) { resolve() } }。then的执行时机:new Promise(resolve => resolve(promise))的then()回调会被推迟两个事件循环。浏览器发现
resolve的是另一个promise时(假设称原promise为promiseA,另一个promise为promiseB),会创建一个名为 PromiseResolveThenableJob的任务去处理promiseB,而这个任务是一个微任务,等到promiseB被resolved之后,会再生成去处理promiseA(resolvePromiseA)的微任务,执行完resolvePromiseA微任务,才会执行promiseA的then,因此推迟了两个事件循环,而Promise.resolve()的then正常执行,是因为参数是promiseB时,会不做处理返回promiseB本身。上面的return Promise.resolve(4);就是这个道理。
# 手撸一次Promise
/* eslint-disable no-lonely-if */
/* eslint-disable no-use-before-define */
// Promise的三种状态
const PROMISE_STATES = {
PENDING: 'pending',
FULFILLED: 'fulfilled',
REJECTED: 'rejected',
};
/**
* 判断目标是否是一个非null的对象
* @param {*} val 待判断的目标
* @returns {boolean}
*/
function isObject(val) {
return val && typeof val === 'object';
}
/**
* 按照3.4的解释,只有x完全符合当前Promise/A+实现时,才认定x是一个Promise实例
* 所以这里直接用instanceof判断实例关系
* @param {*} val 待判断的目标
* @returns {boolean}
*/
function isPromise(val) {
return val instanceof MyPromise;
}
/**
* Promise的回调必须在执行上下文栈只包含platform code(所谓的
* platform code就是指js引擎,宿主环境,Promise实现等代码)时执行
* 所以可以用微任务microtask实现(假设用宏任务macrotask实现
* 浏览器可能已经经过了一轮渲染,不能满足在Promise决议后需要马上操作DOM的场景)
* 这里用process.nextTick和MutationObserver来实现一个兼容Node和浏览器的nextTick。
* 新版本浏览器支持通过queueMicrotask方法创建微任务。
* @param {function} callback
*/
function nextTick(callback) {
if (typeof process === 'object'
&& process !== null
&& typeof process.nextTick === 'function') {
process.nextTick(callback);
} else {
const observer = new MutationObserver(callback);
const textNode = document.createTextNode('1');
observer.observe(textNode, {
// 监视指定目标节点或子节点树中节点所包含的字符数据的变化
characterData: true,
});
textNode.data = '2';
}
}
/**
* 封装Promise状态转移的过程
* @param {MyPromise} promise 发生状态转移的Promise实例
* @param {*} targetState 目标状态
* @param {*} value 伴随状态转移的值,可能是fulfilled的值,也可能是rejected的原因
*/
function transition(promise, targetState, value) {
if (promise.state === PROMISE_STATES.PENDING
&& targetState !== PROMISE_STATES.PENDING) {
// 2.1
// state只能由pending转为其他态,状态转移后,state和value的值不再变化
// 不可配置 不可修改 不可枚举
Object.defineProperty(promise, 'state', {
configurable: false,
writable: false,
enumerable: false,
value: targetState,
});
if (targetState === PROMISE_STATES.FULFILLED) {
Object.defineProperty(promise, 'value', {
configurable: false,
writable: false,
enumerable: false,
value,
});
nextTick(() => {
// then可能会被调用多次,所以异步回调应该用数组来维护
promise.fulfillQueue.forEach(({ handler, chainedPromise }) => {
try {
if (typeof handler === 'function') {
const adoptedValue = handler(value);
// 异步回调返回的值将决定衍生的Promise的状态
resolvePromiseWithValue(chainedPromise, adoptedValue);
} else {
// 存在调用了then,但是没传回调作为参数的可能
// 此时衍生的Promise的状态直接采纳其关联的Promise的状态
transition(chainedPromise, PROMISE_STATES.FULFILLED, promise.value);
}
} catch (error) {
// 如果回调抛出了异常,此时直接将衍生的Promise的状态转移为rejected
// 并用异常error作为reason
transition(chainedPromise, PROMISE_STATES.REJECTED, error);
}
});
promise.fulfillQueue = [];
});
} else if (targetState === PROMISE_STATES.REJECTED) {
Object.defineProperty(promise, 'reason', {
configurable: false,
writable: false,
enumerable: false,
value,
});
nextTick(() => {
promise.rejectQueue.forEach(({ handler, chainedPromise }) => {
try {
if (typeof handler === 'function') {
const adoptedValue = handler(value);
resolvePromiseWithValue(chainedPromise, adoptedValue);
} else {
transition(chainedPromise, PROMISE_STATES.REJECTED, promise.reason);
}
} catch (error) {
transition(chainedPromise, PROMISE_STATES.REJECTED, error);
}
});
promise.rejectQueue = [];
});
}
}
}
/**
* Promise决议过程
* @param {MyPromise} promise 待决议的Promise实例
* @param {*} x 决议使用的值
* @param {array} thenableValues thenable值的数组,用于检测是否出现thenable cycle
*/
function resolvePromiseWithValue(promise, x, thenableValues = []) {
if (promise === x) {
// 2.3.1
// 由于Promise采纳状态的机制,这里必须进行全等判断,防止出现死循环
transition(promise, PROMISE_STATES.REJECTED, new TypeError('promise and x cannot refer to the same object.'));
} else if (isPromise(x)) {
// 2.3.2
// 如果x是一个Promise实例,则跟踪并采纳其状态
if (x.state !== PROMISE_STATES.PENDING) {
// 假设x的状态已经发生转移,则直接采纳其状态
// 这样在执行Promise那道题的结果就为0142356
transition(promise, x.state, x.state === PROMISE_STATES.FULFILLED ? x.value : x.reason);
// 如果非得还原原生Promise的输出,这样在执行Promise那道题的结果就为0123456
// 用两个nextTick hack一下,保证execution context stack为空再安排微任务
// nextTick(() => {
// // 第一个nextTick,能保证execution context stack为空
// nextTick(() => {
// // 第二个nextTick,保证以微任务的形式安排新的任务
// transition(promise, x.state, x.state === PROMISE_STATES.FULFILLED ? x.value : x.reason);
// });
// });
} else {
// 假设x的状态还是pending,则只需等待x决议后再进行promise的状态转移
// 而x决议的结果是不定的,所以两种情况我们都需要进行观察
// 这里用一个.then很巧妙地完成了观察动作
x.then((value) => {
// x决议为fulfilled,由于callback传过来的value是不确定的类型,所以需要递归求取状态
resolvePromiseWithValue(promise, value, thenableValues);
}, (reason) => {
// x决议为rejected
transition(promise, PROMISE_STATES.REJECTED, reason);
});
}
} else if (isObject(x) || typeof x === 'function') {
// 2.3.3
// 如果x是一个对象或函数,则要考虑x是否满足thenable机制
// isInvoked用来保证只执行一次,用以满足2.3.3.3.3和2.3.3.3.4
// 如果同时调用resolvePromise和rejectPromise,或多次调用同一个参数,则第一个调用优先,任何进一步的调用都将被忽略
let isInvoked = false;
try {
// 2.3.3.1 让then成为x.then
const { then } = x;
if (typeof then === 'function') {
// 如果then是一个函数,按thenable机制走一遍
// 2.3.3.3 x作为调用then的this
// 这里不使用x.then,有可能第二次取值会出错
then.call(x, (value) => {
if (!isInvoked) {
isInvoked = true;
// 传给then的第一个参数即resolvePromise被调用
// 3.6 如果thenable出现了环,状态转为rejected,reason为TypeError
if (thenableValues.indexOf(value) !== -1) {
transition(promise, PROMISE_STATES.REJECTED, new TypeError('there is a thenable cycle that will lead to infinite recursion.'));
} else {
// 2.3.3.3.1 value可能还是promise 再次调用决议程序
thenableValues.push(value);
resolvePromiseWithValue(promise, value, thenableValues);
}
}
}, (reason) => {
// 传给then的第一个参数即rejectPromise被调用
// 2.3.3.3.2 这里如果为promise的话,依旧会直接reject,拒绝的原因就是promise。并不会等到promise被解决或拒绝
if (!isInvoked) {
isInvoked = true;
transition(promise, PROMISE_STATES.REJECTED, reason);
}
});
} else {
// 2.3.3.4 如果then不是一个函数,用x解决promise
if (!isInvoked) {
isInvoked = true;
transition(promise, PROMISE_STATES.FULFILLED, x);
}
}
} catch (error) {
if (!isInvoked) {
// 2.3.3.2 & 2.3.3.3.4 在这里一起捕获
isInvoked = true;
transition(promise, PROMISE_STATES.REJECTED, error);
}
}
} else {
// 2.3.4 如果x不是一个对象或函数,用x解决promise
transition(promise, PROMISE_STATES.FULFILLED, x);
}
}
class MyPromise {
constructor(executor) {
// 'pending', 'fulfilled', 'rejected'
this.state = PROMISE_STATES.PENDING;
// value和reason默认是undefined,这里先不赋值
// this.value = undefined;
// this.reason = undefined;
this.fulfillQueue = [];
this.rejectQueue = [];
// 构造Promise实例后,立刻调用executor
// 如果直接调用的话,普通函数this指向的是window或者undefined
// 使用用箭头函数 或者 在这里bind就可以让this指向当前实例对象
// resolve = () => {} reject = () => {}
try {
executor(this.resolve.bind(this), this.reject.bind(this));
} catch (error) {
throw new TypeError('MyPromise resolver undefined is not a function');
}
}
/**
* MyPromise.resolve()
* @param {*} value
* @returns {MyPromise}
*/
static resolve(value) {
return new MyPromise((resolve) => {
resolve(value);
});
}
/**
* MyPromise.reject()
* @param {*} reason
* @returns {MyPromise}
*/
static reject(reason) {
return new MyPromise((resolve, reject) => {
reject(reason);
});
}
/**
* MyPromise.all([]) 所有都成功
* @param {*} iterable
* @returns {MyPromise}
*/
static all(iterable) {
const tasks = Array.from(iterable);
if (tasks.length === 0) {
// 空值返回已完成
return MyPromise.resolve([]);
}
if (tasks.every((task) => !(task instanceof MyPromise))) {
// 没有MyPromise实例则返回一个异步的完成
return new MyPromise((resolve, reject) => {
nextTick(() => {
resolve(tasks);
});
});
}
return new MyPromise((resolve, reject) => {
const values = new Array(tasks.length).fill(null);
let fulfillCount = 0;
tasks.forEach((task, index, arr) => {
if (task instanceof MyPromise) {
task.then((value) => {
fulfillCount++;
values[index] = value;
// 因为then里面都是加到后面的微任务队列
// 所以不用在else条件里面去判断是不是全部fulfilled状态
if (fulfillCount === arr.length) {
resolve(values);
}
}, (reason) => {
reject(reason);
});
} else {
// 不用在此判断是否全部完成
fulfillCount++;
values[index] = task;
}
});
});
}
/**
* MyPromise.allSettled([])
* 所有状态都敲定,即都是fulfilled或rejected
* @param {*} iterable
* @returns
*/
static allSettled(iterable) {
const tasks = Array.from(iterable);
if (tasks.length === 0) {
return MyPromise.resolve([]);
}
if (tasks.every((task) => !(task instanceof MyPromise))) {
// 没有MyPromise实例则返回一个异步的完成
return new MyPromise((resolve, reject) => {
nextTick(() => {
resolve(tasks.map((task) => ({
value: task,
status: PROMISE_STATES.FULFILLED,
})));
});
});
}
return new MyPromise((resolve, reject) => {
const values = new Array(tasks.length).fill(null);
let settledCount = 0;
tasks.forEach((task, index, arr) => {
if (task instanceof MyPromise) {
task.then((value) => {
settledCount++;
values[index] = {
value,
status: PROMISE_STATES.FULFILLED,
};
if (settledCount === arr.length) {
resolve(values);
}
}, (reason) => {
settledCount++;
values[index] = {
value: reason,
status: PROMISE_STATES.REJECTED,
};
if (settledCount === arr.length) {
resolve(values);
}
});
} else {
settledCount++;
values[index] = {
value: task,
status: PROMISE_STATES.FULFILLED,
};
}
});
});
}
/**
* MyPromise.any([])
* 返回第一个成功的值
* @param {*} iterable
* @returns
*/
static any(iterable) {
const tasks = Array.from(iterable);
// 空值返回一个失败的
if (tasks.length === 0) {
return MyPromise.reject(new Error('All promises were rejected'));
}
// 不包含任何一个MyPromise实例 返回一个成功的
if (tasks.every((task) => !(task instanceof MyPromise))) {
// 没有MyPromise实例则返回一个异步的完成
return new MyPromise((resolve, reject) => {
nextTick(() => {
resolve(tasks[0]);
});
});
}
// 第一个成功的被返回 全失败则失败
return new MyPromise((resolve, reject) => {
const values = new Array(tasks.length).fill(null);
let rejectedCount = 0;
tasks.forEach((task, index, arr) => {
if (task instanceof MyPromise) {
task.then((value) => {
resolve(value);
}, (reason) => {
rejectedCount++;
values[index] = reason;
if (rejectedCount === arr.length) {
// eslint-disable-next-line no-undef
reject(new AggregateError(values, 'No Promise in Promise.any was resolved'));
}
});
} else {
rejectedCount++;
values[index] = task;
}
});
});
}
/**
* MyPromise.race([])
* 返回第一个成功或失败的值(race赛跑)
* @param {*} iterable
* @returns
*/
static race(iterable) {
const tasks = Array.from(iterable);
if (tasks.length === 0) {
// 没得值返回的是一个pending状态的promise
return new MyPromise(() => { });
}
if (tasks.every((task) => !(task instanceof MyPromise))) {
// 没有MyPromise实例则返回一个异步的完成
return new MyPromise((resolve, reject) => {
nextTick(() => {
resolve(tasks[0]);
});
});
}
return new MyPromise((resolve, reject) => {
tasks.forEach((task, index, arr) => {
if (task instanceof MyPromise) {
task.then((value) => {
resolve(value);
}, (reason) => {
reject(reason);
});
}
});
});
}
// 触发状态转移是靠调用resolve()或reject()实现的。当resolve()被调用时,
// 当前Promise也不一定会立即变为Fulfilled状态,因为传入resolve(value)方法的
// value有可能也是一个Promise,这个时候,当前Promise必须追踪传入的这个Promise
// 的状态,整个确定Promise状态的过程是通过Promise Resolution Procedure(决议程序)
// 算法实现的,具体细节封装到了下面代码中的resolvePromiseWithValue函数中。
// 当reject()被调用时,当前Promise的状态就是确定的,一定是Rejected,此时可以通过
// transition函数(封装了状态转移的细节)将Promise的状态进行转移,并执行后续动作
// Promise Resolution Procedure算法是一种抽象的执行过程,它的语法形式是
// [[Resolve]](promise, x),接受的参数是一个Promise实例和一个值x,通过值x的可能性,
// 来决定这个Promise实例的状态走向
/**
* @param {*} value
*/
resolve(value) {
resolvePromiseWithValue(this, value);
}
/**
* @param {*} reason
*/
reject(reason) {
transition(this, PROMISE_STATES.REJECTED, reason);
}
/**
* @param {*} onFulfilled
* @param {*} onRejected
* @returns
*/
then(onFulfilled, onRejected) {
// 需要返回一个新的Promise实例,供链式调用
const promise2 = new MyPromise(() => { });
if (this.state === PROMISE_STATES.FULFILLED) {
// 已经是fulfilled了,nextTick进入新Promise的决议程序
nextTick(() => {
try {
if (typeof onFulfilled === 'function') {
const adoptedValue = onFulfilled(this.value);
resolvePromiseWithValue(promise2, adoptedValue);
} else {
transition(promise2, PROMISE_STATES.FULFILLED, this.value);
}
} catch (error) {
transition(promise2, PROMISE_STATES.REJECTED, error);
}
});
} else if (this.state === PROMISE_STATES.REJECTED) {
// 已经是rejected了,nextTick进入新Promise的决议程序
nextTick(() => {
try {
if (typeof onRejected === 'function') {
const adoptedValue = onRejected(this.reason);
resolvePromiseWithValue(promise2, adoptedValue);
} else {
transition(promise2, PROMISE_STATES.REJECTED, this.reason);
}
} catch (error) {
transition(promise2, PROMISE_STATES.REJECTED, error);
}
});
} else {
// 否则进入到待执行队列中
// 被链式调用了多次,后一次都需要依赖前一次的执行结果,所以用队列维护
this.fulfillQueue.push({
handler: onFulfilled,
chainedPromise: promise2,
});
this.rejectQueue.push({
handler: onRejected,
chainedPromise: promise2,
});
}
// 返回它
return promise2;
}
/**
* @param {*} onRejected
* @returns
*/
catch(onRejected) {
return this.then(undefined, onRejected);
}
/**
* @param {*} onFinally
* @returns
*/
finally(onFinally) {
return this.then(
(value) => MyPromise.resolve(onFinally()).then(() => value),
(reason) => MyPromise.resolve(onFinally()).then(() => { throw reason; }),
);
}
}
export default MyPromise;
// promises-aplus-tests
// const deferred = function () {
// let resolve;
// let reject;
// return {
// promise: new MyPromise((res, rej) => {
// resolve = res;
// reject = rej;
// }),
// resolve,
// reject,
// };
// };
// module.exports = { deferred };
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
promises-aplus-tests (opens new window):872 passing (16s)