实现你自己的Promise
在现代化前端开发中,经常会用到Promise
模式,Promise
最大的好处就是可以使异步代码看起来如同步般清新易读,从而从回调地狱中解脱出来,ES6
已经原生支持Promise
对象,但在未支持的浏览器中还需要通过 polyfill 模拟实现。下面一起实现一个Promise
。
一般我们用Promise
会写成类似下面的样子:
const ins = new Promise((resolve, reject) => {
// ...
});
ins.then((res) => {
// ...
}, (ex) => {});
在Promise
中一共存在三种状态,PENDING
, FULFILLED
,REJECTED
,在实例化一个Promise
后,它的状态会变成PENDING
,执行resolve
或者reject
方法会把状态改成FULFILLED
或者REJECTED
,此过程不可逆,也就是说每个Promise
只能调用一次resolve
或者reject
。
先来搭个骨架:
const PENDING = "PENDING",
FULFILLED = "FULFILLED",
REJECTED = "REJECTED";
function Promise(resolver) {
if (!isFunction(resolver)) {
throw new TypeError("TypeError: resolver must be a function");
}
// 实例的值
this.value = null;
// 实例的状态
this.status = PENDING;
// 缓存then和catch中传入的方法
this._doneCallbacks = [];
this._failCallbacks = [];
// 在执行resolver内部抛出异常, try ... catch 包裹
try {
resolver(resolve.bind(this), reject.bind(this));
} catch (ex) {
reject.bind(this)(ex);
}
}
Promise.prototype = {
constructor: Promise,
then: function(onFulfilled, onRejected) {},
catch: function(onRejected) {}
};
function isFunction(obj) {
return typeof obj === "function";
}
上面实现了一个Promise
构造器并且在它原型上声明了then
和catch
两个空方法,下面我们一起来实现一下then
和catch
两个方法, Promise
中的then
支持调用,所以then
方法需要返回一个Promise
实例。
Promise.prototype = {
constructor: Promise,
// then方法接受onFulfilled, onRejected两个回调, 并且返回一个新的Promise实例
then: function(onFulfilled, onRejected) {
// 要返回的新Promise
let ins = new Promise(() => {});
if (isFunction(onFulfilled)) {
// 将onFulfilled缓存到_doneCallbacks中
this._doneCallbacks.push(makeCallback(ins, onFulfilled, "resolve"));
}
if (isFunction(onRejected)) {
this._failCallbacks.push(makeCallback(ins, onFulfilled, "reject"));
}
return ins;
},
catch: function(onRejected) {
return this.then(null, onRejected);
}
};
在实例化一个Promise
时,我们的resolver
有resolve
和reject
两个参数(也就是上面构造器中的最后),在实例化完成后其中有一个将被立即调用,用于结束当前Promise
。下面一起实现下resolve
和reject
,这两个函数完成的功能就是接受resolver
中异步处理返回的值并且传递给then
或者catch
,并且将当前PENDING
的状态改掉,刚才上面说到Promise
的状态从PENDING
到FULFILLED
或者REJECTED
是一个不可逆的过程,所以为了确保resolve
或者reject
不被多次调用,需要在方法开头提前判断下status
是不是PENDING
状态。
function resolve(data) {
if (this.status !== PENDING) {
return;
}
this.value = data;
this.status = FULFILLED;
run.call(this);
}
function reject(ex) {
if (this.status !== PENDING) {
return;
}
this.value = ex;
this.status = REJECTED;
run.call(this);
}
在resolve
和reject
中,我们都调用了run
方法,该方法的作用是用于触发接下来的Promise
的执行。run
函数中需要注意的一点是,需要异步执行相关的回调函数,下面一起来实现一个run
方法,需要注意的是每个Promise
只能被执行一次,所以在run
方法最后,把_doneCallbacks
和_failCallbacks
置空。
function run() {
if (this.status === PENDING) {
return;
}
// 获取到异步处理后的值, 根据当前状态获取要执行的回调
const { value } = this,
callbacks = this.status === FULFILLED ? this._doneCallbacks : this._failCallbacks;
// Promise异步执行
let timeout = setTimeout(() => {
for (let fn of callbacks) {
// 值穿透到每个then或者catch
fn(value);
}
clearTimeout(timeout);
});
this._doneCallbacks = [];
this._failCallbacks = [];
}
上面run
里执行的每个fn
都是then
方法中push
进_doneCallbacks
或_failCallbacks的
makeCallback返回值,
makeCallback`是整个代码中比较复杂的一部分,下面一起看下具体实现。
function makeCallback(promise, callback, action) {
return function promiseCallback(value) {
// callback是个函数
if (isFunction(callback)) {
// 定义个变量去接受返回值
// 防止抛出异常, try ... catch 包裹, catch 时直接调用reject
let x;
try {
x = callback(value);
} catch (ex) {
reject.bind(promise)(ex);
}
// callback中返回的是this, 会引发死循环
// 抛出类型异常到reject
/**
* const ins = new Promise((resolve, reject) => {});
* ins.then(() => {
* return ins;
* });
*/
if (x === promise) {
let reason = new TypeError("TypeError: The return value could not be same with the promise");
reject.bind(promise)(reason);
} else if (x instanceof Promise) {
// callback中返回一个新的Promise
// 执行该Promise的then和catch
/**
* const ins = new Promise((resolve, reject) => {});
* ins.then(() => {
* return new Promise((resolve, reject) => {});
* });
*/
x.then((data) => {
resolve.bind(promise)(data);
}, (ex) => {
reject.bind(promise)(ex);
});
} else {
let then;
(function resolveThenable(x) {
// 如果返回的是一个Thenable对象(Promise)
if (x && (typeof x === "object" || isFunction(x))) {
try {
then = x.then;
} catch (ex) {
reject.bind(promise)(ex);
return;
}
if (isFunction(then)) {
// 调用Thenable对象的then方法时,传递进去的resolvePromise和rejectPromise方法(及下面的两个匿名方法)
// 可能会被重复调用。 但Promise+规范规定这两个方法有且只能有其中的一个被调用一次,多次调用将被忽略
// 此处通过invoked来处理重复调用
let invoked = false;
try {
then.call( x, (y) => {
// 如果已经被调用了,直接return掉
if (invoked) {
return;
}
invoked = true;
// 避免死循环
if (y === x) {
throw new TypeError("TypeError: The return value could not be same with the previous thenable object");
}
// y仍有可能是thenable对象,递归调用
resolveThenable(y);
}, (e) => {
if (invoked) {
return;
}
invoked = true;
reject.bind(promise)(e);
}
);
} catch (e) {
// 如果resolvePromise和rejectPromise方法被调用后,再抛出异常,则忽略异常
// 否则用异常对象reject此Promise对象
if (!invoked) {
reject.bind(promise)(e);
}
}
} else {
resolve.bind(promise)(x);
}
} else {
resolve.bind(promise)(x);
}
}(x));
}
} else {
// 根据action判断执行resolve或者reject
action === "resolve" ? resolve.bind(promise)(value) : reject.bind(promise)(value);
}
};
}
上面就是我对于一个Promise
的实现,完整代码请移步我的GitHub
参考: