Skip to main content

自己动手实现 ES6 Promise

· 11 min read

前言

为了增强对 ES6 Promise工作方式的理解,我实现了一个自己的ES6Promise类,接口与 ES6 的Promise类一致(包含构造函数、thencatchresolvereject), 并且符合 Promise/A+ 规范(通过了规范的全部测试用例) 。下面从简单开始,一步步实现一个自己的Promise

下面是最终实现的ES6Promise使用示例:

import {ES6Promise} from './es6-promise'


let p1 = new ES6Promise((resolve, reject) => {
resolve(1);
});

let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(999);
}, 1000);
});

ES6Promise.resolve(1)
.then(value => value + 1, reason => reason)
.then(value => ES6Promise.reject(p2), reason => reason)
.catch((value) => console.log(value)); // 999

创建 Promise

回想 ES6 的 Promise对象构造函数,它要求传入一个执行器,执行器有两个参数resolvereject,两个参数都是函数类型,我们可以在执行器中调用这两个方法,将Promise变为resolvedrejected。先实现这个一步,代码如下:

const State = {
pending: 0,
resolved: 1,
rejected: 2
}

class ES6Promise {
constructor(executor) {
this._state = State.pending; // 保存状态,取值为 State 之一
this._value = undefined; // 保存 resolve 或 reject 时所传入的值
this._callbacks = []; // 保存状态监听函数,promise 状态变化时调用

if (typeof executor === 'function') {
let resolve = (value) => {
this._transition(State.resolved, value);
};

let reject = (value) => {
this._transition(State.rejected, value);
};
executor(resolve, reject);
}
}

// 状态转移
_transition(state, value) {
if (this._state === State.pending) {
this._state = state;
this._value = value;
this._callbacks.forEach(callback => callback());
}
}
}

使用:

let promise1 = new ES6Promise((resolve, reject) => {
resolve(1);
});

首先,在MyPromise构造函数中声明了两个成员变量statusdata,分别表示MyPromise的状态和数据。根据Promise规范,其状态只能有三种:pendingresolvedrejected,初始状态是pending,一旦变化为resolvedrejected状态后,其状态不再变化。所以,初始设置statusundefined,当调用resolve()reject()时,内部先判断其状态,如果状态已经发生变化,则直接返回,这样保证其状态不会被修改。否则,将状态标记为resolvedrejected,同时保存数据。

实现 then 方法

Promise对象可以链式调用then()方法,这得益于then()返回的也是Promise对象(准确说是thenable对象,即包含then方法的对象),例如:

let promise1 = new ES6Promise((resolve, reject) => {
resolve(1);
});

let promise2 = promise1.then(function onResolved(){}, function onRejected() {});

所以下面ES6Promisethen函数实现中,首先创建并返回一个新的ES6Promise对象promise2,在传入promise2构造函数的执行器内部,通过resolvereject方法修改promise2的状态。promise2状态何时变化,取决于当前promise1的状态,如果promise1状态是pending,则等待promise1resolvedrejected时执行scheduleFn(),否则立即执行scheduleFn()

scheduleFn()方法主要工作是,根据promise1当前状态是resolved(或rejected),调用then(onResolved, onRejected)方法参数中的onResolved(promise1.value)(或onRejected(promise1.value)),以promise1的内部值作为参数,返回结果传递给promise2resolve(或reject)方法,从而改变promise2的状态和内部值。按 Promise/A+ 规范 scheduleFn()必须是异步执行的,所以这里通过setTimeout()方法,让其在下个事件循环中处理。

class ES6Promise {
// 省略重复代码...

then(onResolved, onRejected) {
let self = this;

let promise2 = new ES6Promise((resolve, reject) => {
let scheduleFn = () => {
setTimeout(() => {

onResolved = typeof onResolved === 'function' ? onResolved : v => v;
onRejected = typeof onRejected === 'function' ? onRejected : v => {throw v};
try {
if (self._state === State.resolved) {
resolve(onResolved(self._value));
} else {
resolve(onRejected(self._value));
}
} catch (e) {
reject(e);
}
});
}

if (this._state === State.pending) {
this._callbacks.push(scheduleFn);
} else {
scheduleFn();
}
});

return promise2;
}
}

实现 then 方法 v2

上面实现的then()方法中, 直接将onResolved()(或onRejected())的返回值,传递给resolve(或reject),改变promise2的状态和内部值。这里有一个问题,如果onResolved()(或onRejected())返回的也是一个Promise对象(或thenable对象),那么promise2不会等到这个返回的Promise对象resolved或的rejected后才执行,而是将返回的Promise对象作为promise2的内部值。看下面例子,最后一个then()方法执行后应该输出2才符合预期,而实际输出的是ES6Promise对象实例:

let p1 = new ES6Promise((resolve, reject) => {
resolve(1);
}).then(value => {
return new ES6Promise((resolve, reject) => {
setTimeout(() => resolve(value + 1), 1000);
});
}).then(value => console.log(value)); // 输出:ES6Promise 对象

为了解决上面这个问题,需要对onResolved()(或onRejected())的返回值(暂称之为x)进行判断和处理,这里引入一个resolveProcedure()方法,该方法根据x值的类型,决定何时调用promise2resolvereject方法。如果x是一个thenable对象,则等到该thenable对象状态确定时才调用调用promise2resolvereject方法,否则立即调用promise2resolve,如果中间抛出异常,则立即调用promise2reject方法。代码如下:

class ES6Promise {
// 省略重复代码...

then(onResolved, onRejected) {
let self = this;

let promise2 = new ES6Promise((resolve, reject) => {
let scheduleFn = () => {
setTimeout(() => {
onResolved = typeof onResolved === 'function' ? onResolved : v => v;
onRejected = typeof onRejected === 'function' ? onRejected : v => {throw v};
try {
// 修改这里
let x = self._state === State.resolved ? onResolved(self._value) : onRejected(self._value);
resolveProcedure({ resolve, reject }, x);
} catch (e) {
reject(e);
}
});
}

if (this._state === State.pending) {
this._callbacks.push(scheduleFn);
} else {
scheduleFn();
}
});

return promise2;
}
}


// 根据 x 值,解析 promise 状态 resolveProcedure(promise, x)
function resolveProcedure({ resolve, reject, promise2 }, x) {
// 2.3.1 If promise and x refer to the same object, reject promise with a TypeError as the reason.
if (promise2 === x) {
reject(new TypeError(x));
}

if (x instanceof ES6Promise) { // 2.3.2 If x is a promise, adopt its state
x.then(value => resolveProcedure({resolve, reject, promise2}, value), reason => reject(reason));
} else if ((typeof x === 'object' && x !== null) || (typeof x === 'function')) { // 2.3.3
let resolvedOrRejected = false;
try {
let then = x.then; // 2.3.3.1 Let then be x.then
if (typeof then === 'function') { // 2.3.3 If then is a function, call it with x as this, first argument resolvePromise, and second argument rejectPromise, where:
then.call(x, value => {
if (!resolvedOrRejected) {
resolveProcedure({ resolve, reject, promise2 }, value); // 2.3.3.3.1 If/when resolvePromise is called with a value y, run [[Resolve]](promise, y).
resolvedOrRejected = true;
}
// 2.3.3.3.3 If both resolvePromise and rejectPromise are called, or multiple calls to the same argument are made, the first call takes precedence, and any further calls are ignored.
}, reason => {
if (!resolvedOrRejected) {
reject(reason); // 2.3.3.3.2 If/when rejectPromise is called with a reason r, reject promise with r.
resolvedOrRejected = true;
}
// 2.3.3.3.3 If both resolvePromise and rejectPromise are called, or multiple calls to the same argument are made, the first call takes precedence, and any further calls are ignored.
});
} else { // 2.3.3.4 If then is not a function, fulfill promise with x.
resolve(x);
}
} catch (e) {
if (!resolvedOrRejected) {
// 2.3.3.2 If retrieving the property x.then results in a thrown exception e, reject promise with e as the reason.
// 2.3.3.4 If calling then throws an exception e
reject(e);
}
}
} else {
resolve(x); // 2.3.4 If x is not an object or function, fulfill promise with x.
}
}

修改后,再次运行上面测试代码,结果符合预期:

let p1 = new ES6Promise((resolve, reject) => {
resolve(1);
}).then(value => {
return new ES6Promise((resolve, reject) => {
setTimeout(() => resolve(value + 1), 1000);
});
}).then(value => console.log(value)); // 输出: 2

实现 catch 方法

ES6Promise核心的then方法上面已经实现,catch方法不过是then方法的一种便捷形式,其实现如下:

class ES6Promise {
// 省略重复代码...

catch(onRejected) {
this.then(undefined, onRejected);
}
}

实现 resolve/reject 静态

ES6 的Promise对象还提供了两个静态方法Promise.resolvePromise.reject,通过这两个方法可以很方便的将一般javascript值封装成Promise对象。实现这两个方法也很简单,以Promise.resolve为例,首先这个方法要返回一个新的Promise对象,新的Promise对象解析传入的值,这个解析过程交由resolveProcedure()方法完成,由于这是resolve方法,所以即使value是一个被rejectedPromise,也要将其结果resolve,所以传递给resolveProcedure()方法的第一个参数都是resolve方法。Promise.reject方法实现类似,代码如下:

class ES6Promise {
// 省略重复代码...

static resolve(value) {
return new ES6Promise((resolve, reject) => resolveProcedure({resolve, reject: resolve}, value));
}

static reject(reason) {
return new ES6Promise((resolve, reject) => resolveProcedure({resolve: reject, reject}, reason));
}
}

测试

上面的ES6Promsie通过了 promises-tests 提供的全部测试用例,意味着其完全符合了 Promise/A+ 规范。

可以通过 npm 安装后,查看源码和测试结果:

// 安装
$ npm install es6-promise
// 编译
$ npm run build
// 运行测试用例
$ npm run test

小结

Promise作为社区产物,最终被纳入 ECMAScript 规范,可见其是被大众所接受的。Promise改变了长久以来通过callback编写异步代码的方式,让异步回调以一种更优雅的方式链式调用,并拥有更清晰的错误处理。Promise同时也为generator/yieldasync/await以同步方式编写异步代码提供了基础设施。Promise使用起来很简单,但是涉及到一些复杂或极端的例子,需要对Promise规范理解透彻才能正确得到结果。

最后附上项目地址和仓库地址:

github 地址 : https://github.com/whinc/es6-promise

npm 地址:https://www.npmjs.com/package/whinc-es6-promise

参考