promise.then异步实现
手写:setTimeout
我们手写一个promise/A+规范的Promise时,一般在实现then方法时会采用setTimeout
来实现异步操作:
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (v) => v
onRejected = typeof onRejected === "function" ? onRejected : (v) => {throw v}
if (this.state === "fulfilled") {
setTimeout(() => {
onFulfilled(this.result)
})
} else if (this.state === "rejected") {
setTimeout(() => {
onRejected(this.result)
})
}else if (this.state === "pending") {
this.#handlers.push({
onFulfilled: () => {
setTimeout(() => {
onFulfilled(this.result)
})},
onRejected: () => {
setTimeout(() => {
onRejected(this.result)
})}
})
}
}
源码:使用微任务
源码地址:https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/es.promise.constructor.js
then方法实现路径文件:core-js/packages/core-js/modules/es.promise.constructor.js
与使用 setTimeout 相比,微任务的执行时机更早,因此能够更有效地管理异步操作。
1. 定义 Promise 原型对象的 then 方法
reaction 对象:
- reaction 对象用于封装 then 方法中传入的成功和失败回调函数,以及与之关联的新 Promise 对象。
- 它包含了以下属性:
- ok:用于存储成功回调函数,如果传入的回调函数是可调用的,则将其存储在这里。
- fail:用于存储失败回调函数,如果传入的回调函数是可调用的,则将其存储在这里。
- promise:用于保存 then 方法返回的新 Promise 对象。
- domain:用于存储当前 Promise 对象所属的域(如果支持)。
可以看到,在下述代码中,使用了 microtask微任务(在下面具体解析相关代码) 处理异步操作
var microtask = require('../internals/microtask');
// `Promise.prototype.then` method
Internal.prototype = defineBuiltIn(PromisePrototype, 'then', function then(onFulfilled, onRejected) {
// 获取了当前 Promise 对象的内部状态对象 state,它用于跟踪 Promise 对象的状态、值和回调函数等信息。
var state = getInternalPromiseState(this);
// newPromiseCapability 函数会创建一个新的 Promise 对象,将其赋值给 promise 属性
var reaction = newPromiseCapability(speciesConstructor(this, PromiseConstructor));
state.parent = true;
// 如果 onFulfilled 是一个可调用的函数,则将其赋值给 ok 属性,否则将 true 赋值给 ok,表示不需要执行成功回调函数。
reaction.ok = isCallable(onFulfilled) ? onFulfilled : true;
// 如果 onRejected 是一个可调用的函数,则将其赋值给 fail 属性,否则将 undefined 赋值给 fail,表示不需要执行失败回调函数。
reaction.fail = isCallable(onRejected) && onRejected;
reaction.domain = IS_NODE ? process.domain : undefined;
// 如果是PENDING状态,则将 reaction 对象添加到当前 Promise 对象的 reactions 队列中。
if (state.state === PENDING) state.reactions.add(reaction);
// 否则 通过微任务来异步执行 callReaction 函数,以处理 reaction 对象。
else microtask(function () {
callReaction(reaction, state);
});
return reaction.promise;
});
2. 处理 reaction
对象的核心函数
(重点关注注释部分即可)
var callReaction = function (reaction, state) {
var value = state.value;
var ok = state.state === FULFILLED;
var handler = ok ? reaction.ok : reaction.fail;
var resolve = reaction.resolve;
var reject = reaction.reject;
var domain = reaction.domain;
var result, then, exited;
try {
// 根据当前 Promise 的状态来选择应该执行的处理函数
if (handler) {
// 处理当前 Promise 的状态不是 FULFILLED 的情况
if (!ok) {
if (state.rejection === UNHANDLED) onHandleUnhandled(state);
state.rejection = HANDLED;
}
// 处理特殊情况,即处理函数为 true 的情况,将当前 Promise 的值直接赋给 result
if (handler === true) result = value;
else {
…………(一些domain的代码,省略)
}
// 如果 result 等于 reaction.promise,则说明存在 Promise 链循环,需要拒绝当前 Promise
if (result === reaction.promise) {
reject(new TypeError('Promise-chain cycle'));
// 如果 result 是一个 Promise 对象,则执行链式调用
} else if (then = isThenable(result)) {
call(then, result, resolve, reject);
// 否则 resolve 当前 Promise
} else resolve(result);
} else reject(value);
} catch (error) {
…………
reject(error);
}
};
result === reaction.promise为什么reject?
这个情况表明成功或失败处理函数返回的结果正好是当前 then 方法返回的新 Promise 对象 reaction.promise。
这种情况下,就会判断存在链循环,因为在 Promise 链中,每个 then 方法返回的新 Promise 对象应该是一个新的实例,而不应该与前一个 then 方法返回的 Promise 对象相同。
3. microtask文件实现
(重点关注注释部分)
'use strict';
var global = require('../internals/global');
var safeGetBuiltIn = require('../internals/safe-get-built-in');
var bind = require('../internals/function-bind-context');
var macrotask = require('../internals/task').set;
var Queue = require('../internals/queue');
var IS_IOS = require('../internals/engine-is-ios');
var IS_IOS_PEBBLE = require('../internals/engine-is-ios-pebble');
var IS_WEBOS_WEBKIT = require('../internals/engine-is-webos-webkit');
var IS_NODE = require('../internals/engine-is-node');
var MutationObserver = global.MutationObserver || global.WebKitMutationObserver;
var document = global.document;
var process = global.process;
var Promise = global.Promise;
// 微任务方法获取,现代引擎提供的 queueMicrotask 方法,如果支持直接使用
var microtask = safeGetBuiltIn('queueMicrotask');
var notify, toggle, node, promise, then;
// 不支持queueMicrotask的实现:
if (!microtask) {
var queue = new Queue();
// 执行微任务队列中的任务
var flush = function () {
var parent, fn;
if (IS_NODE && (parent = process.domain)) parent.exit();
// 遍历微任务队列,队列为空循环结束
while (fn = queue.get()) try {
fn();
} catch (error) {
if (queue.head) notify();
throw error;
}
if (parent) parent.enter();
};
// 根据当前环境的特性来选择最适合的微任务实现方式
//不在 iOS、Node.js 和 WebOS Webkit 环境中,同时 MutationObserver 和 document 存在 ,使用MutationObserver监听
if (!IS_IOS && !IS_NODE && !IS_WEBOS_WEBKIT && MutationObserver && document) {
toggle = true;
node = document.createTextNode('');
new MutationObserver(flush).observe(node, { characterData: true });
notify = function () {
node.data = toggle = !toggle;
};
// 不在 iOS Pebble 环境下,且Promise和Promise.resolve存在,创建一个立即resolved的 Promise 对象,并通过 then 方法来执行微任务队列中的任务。
} else if (!IS_IOS_PEBBLE && Promise && Promise.resolve) {
promise = Promise.resolve(undefined);
promise.constructor = Promise;
then = bind(promise.then, promise);
notify = function () {
then(flush);
};
// 如果在 Node.js 环境,使用 process.nextTick 方法来执行微任务队列中的任务。
} else if (IS_NODE) {
notify = function () {
process.nextTick(flush);
};
// 以上所有条件都不满足,那么代码将使用宏任务(macrotask)
// 比如 setImmediate、MessageChannel、window.postMessage、onreadystatechange 或 setTimeout。
} else {
// `webpack` dev server bug on IE global methods - use bind(fn, global)
macrotask = bind(macrotask, global);
notify = function () {
macrotask(flush);
};
}
microtask = function (fn) {
if (!queue.head) notify();
queue.add(fn);
};
}
module.exports = microtask;