Promise(2):手动实现Promise

以 Promise/A+ 作为标准,编写一个可经过标准测试的Promise类库。git

Promise类的结构

  1. Promise对象初始状态为 Pending,在被 resolve 或 reject 时,状态变为 Fulfilled 或 Rejected
  2. resolve接收成功的数据,reject接收失败或错误的数据
  3. Promise对象必须有一个 then 方法,且只接受两个可函数参数 onFulfilled、onRejected
const REJECTED = 'rejected';
const RESOLVED = 'resolved';
const PENDING = 'pending';

class MPromise{
    constructor(resolver){
        if(resolver && typeof resolver !== 'function'){
            throw new Error('MPromise resolver is not function');
        }
        
        this.state = PENDING; //当前promise对象的状态
        this.data = undefined; //当前promise对象的数据(成功或失败)
        this.callbackQueue = []; //当前promise对象注册的回调队列
        
        if(resolver){
           executeResolver.call(this, resolver);
        }
    }
    then(){
        //todo
    }
}
复制代码

因此,一个 Promise构造函数 和一个实例方法then 就是Promise的核心的了,其它的都是Promise的语法糖或者说是扩展github

executeResolver

构造器的初始化,使用 new Promise(function(resolve, reject){...})实例化 Promise 时,去改变promise的状态,是执行 resolve() 或 reject()方法,那么,resolver的两个参数分别是成功的操做函数和失败的操做函数。promise

function executeResolver(resolver){
    let called = false; //状态变动不可逆
    let _this = this;
    function onError(reason){
        if(!called){
            return;
        }
        called = true;
        executeCallback.call(_this,'reject', reason);
    }
    function onSuccess(value){
        if(!called){
            return;
        }
        called = true;
        executeCallback.call(_this, 'resolve', value);
    }
    try{
        resolver(onSuccess, onError);
    }catch(e){
        onError(e);
    }
}
复制代码

这里将上面执行 resolver 的方法抽象出来,内部再将 resovle 和 reject 两个参数包装成 成功和失败的回调。浏览器

executeCallback

由于执行 resolve() 或 reject() 内部主要做用是更改当前实例的状态为 rejected 或 resolved,而后执行当前实例 then() 中注册的 成功或失败的回调函数, 因此从过程上来看,大体是相同的,抽象出来共用安全

function executeCallback(type, x){
    const isResolve = type === 'resolve' ? true : false;
    let thenable;
    
    if(isResolve && typeof x === 'object' && typeof x.then === 'function'){
        try {
            thenable = getThen(x);
        } catch (e) {
            return executeCallback.call(this, 'reject', e);
        }
    }
    if(isResolve && thenable){
        executeResolver.call(this, thenable); //注意是this
    } else {
        this.state = isResolve ? RESOLVED : REJECTED;
        this.data = x;
        this.callbackQueue.forEach(v => v[type](x));
    }
    return this;
}
复制代码
function getThen(obj){
    const then = obj && obj.then;
    if(obj && typeof obj === 'object' && typeof then === 'function'){
        return applyThen(){
            then.apply(obj, arguments)
        }
    }
}
复制代码

then

标准中规定:bash

  1. then 方法必须返回一个新的 Promise实例(ES6中的标准,Promise/A+中没有明确说明)
  2. 为了保证 then中回调的执行顺序,onFulfilled 或 onRejected 必须异步调用
class MPromise{
    ...
    then(onResolved, onRejected){
        //回调不是函数,能够忽略
        if(this.state === RESOLVED && onResolved !== 'function' 
            || this.state === REJECTED && onRejected !== 'function'){
            return this;
        }
        let promise = new MPromise();
        if(this.state !== PENDING){
            var callback = this.state === RESOLVED ? onResolved : onRejected;
            //注意:传入promise,
            //异步调用
            executeCallbackAsync.call(promise, callback, this.data);
        } else {
            this.callbackQueue.push(new CallbackItem(promise, onResolved, onRejected))
        }
        return promise; //必须返回promise,才能链式调用
    }
}
复制代码

executeCallbackAsync

上面将异步调用callback的逻辑抽象成了一个方法executeCallbackAsync ,这个方法主要功能是安全的执行callback方法:app

  1. 若是出错,则自动调用 reject(reason) 方法并更改状态为 rejected,传递错误数据给当前实例then方法中注册的onRejected 回调
  2. 若是成功,则自动调用 resolve(value)方法并更改状态为resolved,传递数据给当前实例then方法中注册的 onResolved 回调
function executeCallbackAsync(callback, value){
    let _this = this;
    setTimeout(() => {
        let res;
        try{
            res = callback(value);
        }catch(e){
            return executeCallback.call(_this, 'reject', e);
        }
        if(res !== _this){
            return executeCallback.call(_this, 'resolve', res);
        } else {
            return executeCallback.call(_this, 'reject', new TypeError('Cannot resolve promise with itself'));
        }
    }, 4);
}
复制代码

注意这里最好不要用 setTimeout ,使用 setTimeout 能够异步执行回调,但其实并非真正的异步线程,而是利用了浏览器的 Event Loop 机制去触发执行回调,而浏览器的事件轮循时间间隔是 4ms ,因此链接的调用 setTimeout 会有 4ms 的时间间隔,而在Nodejs 中的 Event Loop 时间间隔是 1ms,因此会产生必定的延迟,若是promise链比较长,延迟就会越明显,这里能够引入NPM上的 immediate 模块来异步无延迟的执行回调。异步

CallbackItem

上面then中对于回调的处理,使用了一个回调对象来管理注册的回调,将回调按顺序添加至 callbackQueue 队列中,调用时,依次调用。函数

class CallbackItem {
    constructor(promise, onResolved, onRejected) {
        this.promise = promise;
        this.onResolved = typeof onResolved === 'function' ? onResolved : function (v) { 
            return v;
        };
        this.onRejected = typeof onRejected === 'function' ? onRejected : function (v) { 
            throw v; 
        };
    }
    resolve(value) {
        executeCallbackAsync.call(this.promise, this.onResolved, value);
    }
    reject(value) {
        executeCallbackAsync.call(this.promise, this.onRejected, value);
    }
}
复制代码

例子

以上参考深刻理解Promise中的文章,实现MPromiseoop

function fn() {
    let promise1 = new MPromise((resolve, reject) => {
        resolve(1); 
    });
    new MPromise((resolve, reject) => {
        resolve(promise1); //系统执行promise1.then
    }).then(res => {
        console.log(res);
        return 222;
    }).catch(err => {
        console.log(err);
    });
}
fn(); // 1
复制代码

参考文章

深刻理解 Promise (中)

MPromise的git地址

相关文章
相关标签/搜索