Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

自己动手实现 ES6 Promise #2

Open
whinc opened this issue Feb 25, 2018 · 4 comments
Open

自己动手实现 ES6 Promise #2

whinc opened this issue Feb 25, 2018 · 4 comments
Labels

Comments

@whinc
Copy link
Owner

whinc commented Feb 25, 2018

前言

为了增强对 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

参考

@pod4g
Copy link

pod4g commented Mar 12, 2018

mark

@ufolux
Copy link

ufolux commented Mar 29, 2018

👍

@saifeiLee
Copy link

第一个版本的then方法里的try...catch代码段中,else后是不是应该写reject(onRejected(self._value));

@whinc
Copy link
Owner Author

whinc commented Aug 10, 2018

@saifeiLee 代码没问题。

根据 Promise/A+ 规范的 2.2.7 小节,then() 方法的第二个参数 onRejected 只有返回一个 rejected promise 或者抛出异常时,then() 返回的 promise 才会变为 rejected 状态,否则返回的 promise 是 resolved。

例如下面例子会输出 100:

Promise.reject(100).then(undefined, v => v).then(v => console.log(v))
// 输出 100

@whinc whinc added the JS/TS label Dec 14, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants