这篇文章的目的就是想探索一下promise是怎么实现的,因为promise一直在用,但是对于它内部具体是怎么实现的却一直不知,所以就想解一解心中的疑惑,掀开promise的神秘面纱 ^_^
。
首先,我们先通过Promises/A+规范 认识一下promise。
promise是什么
A promise represents the eventual result of an asynchronous operation. —— Promises/A+规范
就是说promise代表一个异步操作的最终结果。与promise进行交互的主要方式是通过then方法,该方法注册回调以接收promise的最终值或无法执行promise的原因。
-
promise的状态
promise必须处于以下三种状态之一:pending、fulfilled、rejected。一旦promise变为fulfilled或rejected,就不能再改变为其他的状态。 -
then 方法
一个promise必须提供一个then方法来访问其当前或最终的值。then方法接受两个参数promise.then(onFulfilled, onRejected)
- onFulfilled, onRejected是可选的参数,如果onFulfilled或 onRejected不是函数,则忽略这个参数
- 如果onFulfilled是一个函数,它必须在promise执行成功后调用,并且以promise的值作为它的第一个参数,不可以调用多次;如果onRejected是一个函数,它必须在promise执行失败后调用。
- 在执行上下文仅包含平台代码的时候onFulfilled和onRejected才能被调用。什么是“平台代码”,
Promises/A+
规范也给出了解释:在实践中要确保onFulfilled和onRejected异步执行,在event loop之后执行then方法。这个可以使用setTimeout
orsetImmediate
等macro-task
机制,或者MutationObserver
orprocess.nextTick
等micro-task
机制来实现, 实际上then是属于micro-task
- then可以在一个promise中调用多次,当 promise 变为或已处于 fulfilled 时,所有 onFulfilled 回调必须按照其通过 then 注册的顺序被执行;当 promise 变为或已处于 rejected 时,所有 onRejected 回调必须按照其通过 then 注册的顺序被执行。
- then必须返回一个新的promise
接下来,我们从promise的构造函数入手。
promise构造函数
Promises/A+ 规范并没有明确给出Promise构造函数的规则,所以我们根据es6 Promise的用法来构造一个构造函数
function Promise (executor) {
this._status = 'PENDING'; // promise 的三种状态:PENDING、FULFILLED、REJECTED
this._value = null; // 存储promise的执行结果
this.onResolvedCB = []; // then() 注册resolve 时的回调函数数组
this.onRejectedCB = []; // then() 注册reject 时的回调函数数组
// resolve和reject是构造函数上的方法,要在内部实现
function resolve(value) {
// ...
}
function rejected(reason) {
// ...
}
try { // executor执行过程中可能会产生错误,所以用try catch 来捕获
executor(resolve, reject); // 根据es6我们知道,Promise一旦新建就会立即执行,所以最后执行了回调函数
} catch (e) {
reject(e);
}
}
构造函数的主体算是出来了,接下来要实现resolve和reject两个函数
根据es6的描述,resolve函数的作用是:将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
function resolve(value) {
this._status = 'FULFILLED'; // 将状态改为已成功('FULFILLED')
this._value = value; // 更新当前value值
for (const fn of this.onResolvedCB) {
fn(value); // 执行所有的回调函数
}
this.onResolvedCB = [];
}
function rejected(reason) {
this._status = 'REJECTED'; // 将状态改为失败('REJECTED')
this._value = reason; // 更新当前value值
for (const fn of this.onRejectedCB) {
fn(reason); // 执行所有的回调函数
}
this.onRejectedCB = [];
}
then方法
接下来看一下then方法的实现
Promise.prototype.then = function(onResolved, onRejected) {
const that = this;
let promise2 = null
// 根据标准,如果参数不是函数就要忽略它。但是我们要处理值的穿透问题,例如:
// new Promise((resolve, reject) => {
// resolve(1);
// }).then().then((val) => {console.log(val)}) 也是要打印出 1 的
// 我们知道then方法回调函数的返回值会作为参数传递给下一个then 方法的回调函数,所以当参数不是函数时,默认是一个又返回值的函数
onResolved = typeof onResolved === 'function' ? onResolved : function(value) { return value; };
onRejected = typeof onRejected === 'function' ? onRejected : function(reason) { return reason; };
// 当状态处于已完成时,调用onResolved, 并且返回一个新的Promise
if (that._status === 'FULFILLED') {
return promise2 = new Promise(function(resolve, reject) {
setTimeout(function() { // 我们知道,then是一个‘micro-task’,回调函数必须在事件循环之后,在一个新的调用栈里异步执行。‘micro-task’ process.nextTick是
try { // node中的函数,所以我们先用setTimeout实现
const x = onResolved(that._value);
resolve(x);
} catch (e) {
reject(e)
}
});
});
}
if (that._status === 'REJECTED') {
return promise2 = new Promise(function(resolve, reject) {
setTimeout(function() {
try {
const x = onRejected(that._value);
resolve(x);
} catch (e) {
reject(e);
}
});
});
}
// 当处于等待状态时,把回调函数分别放入对应当队列,当状态改变时调用
if (that._status === 'PENDING') {
return promise2 = new Promise(function(resolve, reject) {
that.onResolvedCB.push(function(value) {
setTimeout(function() {
try {
const x = onResolved(value);
resolve(x);
} catch (e) {
reject(e)
}
});
});
that.onRejectedCB.push(function(reason) {
setTimeout(function() {
try {
const x = onRejected(reason);
resolve(x);
} catch (e) {
reject(e)
}
})
});
});
}
}
为了使不同的promise(不同的第三方可能会有不同的实现)互相交互,我们遵照规范(The Promise Resolution Procedure)实现一下resolvePromise
function resolvePromise(promise, x, resolve, reject) {
let then = null;
if (promise === x) {
return reject(new TypeError());
}
if (x instanceof Promise) {
if (x._status === 'PENDING') {
x.then(function(value) {
resolvePromise(promise, value, resolve, reject)
}, reject);
} else {
x.then(resolve, reject);
}
return;
}
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
try {
then = x.then;
} catch (e) {
return reject(e);
}
if (typeof then === 'function') {
try {
then.call(x, function(value) {resolve(value)}, function(reason){reject(reason)})
} catch (e) {
reject(e);
}
}
} else {
resolve(x)
}
}
然后用这个方法替换then方法中各个状态回调函数返回值的处理,即:
Promise.prototype.then = function(onResolved, onRejected) {
const that = this;
let promise2 = null
// 根据标准,如果参数不是函数就要忽略它。但是我们要处理值的穿透问题,例如:
// new Promise((resolve, reject) => {
// resolve(1);
// }).then().then((val) => {console.log(val)}) 也是要打印出 1 的
// 我们知道then方法回调函数的返回值会作为参数传递给下一个then 方法的回调函数,所以当参数不是函数时,默认是一个又返回值的函数
onResolved = typeof onResolved === 'function' ? onResolved : function(value) { return value; };
onRejected = typeof onRejected === 'function' ? onRejected : function(reason) { return reason; };
// 当状态处于已完成时,调用onResolved, 并且返回一个新的Promise
if (that._status === 'FULFILLED') {
return promise2 = new Promise(function(resolve, reject) {
setTimeout(function() { // 我们知道,then是一个‘micro-task’,回调函数必须在事件循环之后,在一个新的调用栈里异步执行。‘micro-task’ process.nextTick是
try { // node中的函数,所以我们先用setTimeout实现
const x = onResolved(that._value);
+ resolvePromise(promise2, value, resolve, reject);
} catch (e) {
reject(e)
}
});
});
}
if (that._status === 'REJECTED') {
return promise2 = new Promise(function(resolve, reject) {
setTimeout(function() {
try {
const x = onRejected(that._value);
+ resolvePromise(promise2, value, resolve, reject);
} catch (e) {
reject(e);
}
});
});
}
// 当处于等待状态时,把回调函数分别放入对应当队列,当状态改变时调用
if (that._status === 'PENDING') {
return promise2 = new Promise(function(resolve, reject) {
that.onResolvedCB.push(function(value) {
setTimeout(function() {
try {
const x = onResolved(value);
+ resolvePromise(promise2, value, resolve, reject);
} catch (e) {
reject(e)
}
});
});
that.onRejectedCB.push(function(reason) {
setTimeout(function() {
try {
const x = onRejected(reason);
+ resolvePromise(promise2, value, resolve, reject);
} catch (e) {
reject(e)
}
})
});
});
}
}
以前对Promise的状态改变之后就不可再变的理解有误会,以为Promise抛出错误被catch方法捕获之后后面的then方法就不会执行了,实际上是又返回了一个新的Promise,后面then方法的执行取决于新的Promise的状态。
另外,catch方法也只是then方法的一个语法糖,就相当于
Promise.prototype.then(undefined, onRejected);
可能对Promise的理解有不到位的地方,欢迎小伙伴前来指正~ _
网友评论