面试手写ES6的Promise

参考资源:
【翻译】Promises/A+规范-图灵社区
BAT前端经典面试问题:史上最最最详细的手写Promise教程 - 掘金

一、如何使用 Promise

先看一下 Promise 的用法。

const promise = new Promise((resolve, reject) => {
   setTimeout(() => {
       resolve("success");
   });
});
promise.then(value => { console.log(value) }, reason => { console.log(reason) });

输出:

success

Promise 的构造函数接收了一个回调函数,这个回调就是下面要讲到的执行器(executor),executor 里面的 resolve, reject 也是两个函数,负责改变 Promise 实例的状态和它的值,then 函数中的回调在状态改变后执行,除此之外 then 还支持链式调用。

详见 ES6 Promise 的用法: http://es6.ruanyifeng.com

接下来就开始吧。

一、Promise 的三种状态

Promise 的三种状态:fulfilled(执行态)、rejected(拒绝态)、pending(等待态)。

关于状态转化:

  • 成功时,不可转为其他状态,且必须有一个不可改变的值(value)

例如:new Promise((resolve, reject)=>{resolve(value)}) resolve 为成功,接收参数value,状态改变为 fulfilled,不可再次改变。

  • 失败时,不可转为其他状态,且必须有一个不可改变的原因(reason)

例如:new Promise((resolve, reject)=>{reject(reason)})reject 为失败,接收参数 reason,状态改变为 rejected,不可再次改变。

第一步实现 executor

这块的执行逻辑是 new Promise 参数是一个函数体,executor 形参接收调用executor 调用的时候需要传两个函数体过去,等待调用。

class _Promise {
    constructor(executor){
        // 校验executor
        if(typeof executor !== "function"){
            throw new Error(`Promise resolver ${executor} is not a function!`);
            // new Promise(1); // Uncaught TypeError: Promise resolver 1 is not a function
        };
        const resolve = (value)=>{
            console.log(value);
        };
        const reject = (reason)=>{
            console.log(reason);
        };
        executor(resolve,reject);
    }
}
new _Promise((resolve,reject)=>{
    resolve("success");
});


第二步添加 Promise 的状态:

class _Promise {
    constructor(executor){
        // 校验executor
        if(typeof executor !== "function"){
            throw new Error(`Promise resolver ${executor} is not a function!`);
        };
        
        this.value = undefined; //终值=>resolve的值
        this.reason = undefined;//拒因=>reject的值
        this.state = "pending";//状态

        const resolve = (value)=>{
            // 成功后的一系列操作(状态的改变,成功回调的执行)
            if(this.state === "pedding"){
                this.state = "fulfilled";
                this.value = value;
            };
        };
        const reject = (reason)=>{
            // 失败后的一系列操作(状态的改变,成功回调的执行)
            if(this.state === "pedding"){
                this.state = "rejected";
                this.reason = reason;
            }
        };
        executor(resolve,reject);
    }
}
new _Promise((resolve,reject)=>{
    resolve("success");
});
三、then 方法

then 方法,里面有两个参数:onFulfilled(成功时 resolve 触发)onFulfilled(失败时 reject 触发)

  • 校验 onFulfilled 和 onFulfilled 是不是函数。是函数就直接运行,不是函数把值变成函数。
  • resolve 函数执行的时候,把 state 的状态由 pending 变为 fulfilled,then 方法里面
    state 的状态为 fulfilled 则执行 onFulfilled,同时传入 this.value。
  • reject 函数执行的时候,把 state 的状态由 pending 变为 rejected,then 方法里面
    state 的状态为 rejected则执行 onRejected,同时传入 this.value。
class _Promise {
    constructor(executor){
        // 校验executor
        if(typeof executor !== "function"){
            throw new Error(`Promise resolver ${executor} is not a function!`);
        };

        this.value = undefined; //终值=>resolve的值
        this.reason = undefined;//拒因=>reject的值
        this.state = "pending";//状态

        const resolve = (value)=>{
            // 成功后的一系列操作(状态的改变,成功回调的执行)
            if(this.state === "pending"){
                this.state = "fulfilled";
                this.value = value;
            };
        };
        const reject = (reason)=>{
            // 失败后的一系列操作(状态的改变,成功回调的执行)
            if(this.state === "pending"){
                this.state = "rejected";
                this.reason = reason;
            }
        };
        executor(resolve,reject);
    }
    then(onFulfilled,onRejected){

        // onFulfilled未传值或传的值不是function的时候
        // 自动把onFulfilled变成一个函数
        if(typeof onFulfilled !== "function"){
            onFulfilled = value => value;
        };

        //onRejected未传值或传的值不是function的时候
        //自动把onFulfilled变成一个函数,并抛出错误
        if(typeof onRejected !== "function"){
            onRejected =  reason => { throw reason }
        };

        if(this.state === "fulfilled"){
            onFulfilled(this.value);
        };

        if(this.state === "rejected"){
            onRejected(this.reason);
        };
    }
};
四、调整执行策略

写到这里大致实现了 Promise ,但是需要一些小小的修正,因为上面有不对的地方。

  1. 执行顺序问题
    ES6 的 promise 执行下面代码输出结果为:1 2 4 3
console.log(1);
new Promise((resolve,reject)=>{
    console.log(2);
    resolve(3);
})
.then(
    value=>console.log(value)
);
console.log(4);

当使用我们自己手写的 Promise 运行时输出:1 2 3 4

console.log(1);
new _Promise((resolve,reject)=>{
    console.log(2);
    resolve(3);
})
.then(
    value=>console.log(value)
);
console.log(4);

学过 JS 的事件执行机制的同学应该猜到哪里出现了问题了,没错我们手写的 then 这个方法是同步的,不会等到同步执行完再去执行 then 里面的函数。我们可以用 setTimeout 来解决这个问题。

setTimeout(()=>onFulfilled(this.value));
setTimeout(()=>onRejected(this.reason));
  1. 抛出错误
new Promise((resolve,reject)=>{
    throw new Error("随便抛出一个错误");
    resolve(3);
})
.then(
    value=>console.log(value),
    reason=>console.log(reason)
);
ES6抛出错误

手写的直接报错:


手写的直接报错

原因在于 ES6 的错误是通过 reject 来触发的,我们手写的 Promise 遇到错误直接就抛出了,改写如下:

try{
    executor(resolve,reject);//不抛出错误
}catch(err){
    reject(err);//让reject 抛出
};
  1. 使用延时器不输出
new _Promise((resolve,reject)=>{
    setTimeout(()=>{
        resolve(3);
    });
})
.then(
    value=>console.log(value)
);

ES6 会输出 3 ,手写的不输出结果。原因很简单,setTimeout 的执行会在 then 之后state 的状态是 pending,而 then 里面函数是根据 state 改变之后的状态来执行的。

class _Promise {
    constructor(executor){
        // 校验executor
        if(typeof executor !== "function"){
            throw new Error(`Promise resolver ${executor} is not a function!`);
        };

        this.value = undefined; //终值=>resolve的值
        this.reason = undefined;//拒因=>reject的值
        this.state = "pending";//状态

        this.onFulfilledCallbacks = [];// 成功回调
        this.onRejectedCallbacks = [];// 失败回调

        const resolve = (value)=>{
            // 成功后的一系列操作(状态的改变,成功回调的执行)
            if(this.state === "pending"){
                this.state = "fulfilled";
                this.value = value;
                this.onFulfilledCallbacks.forEach(fn=>fn(this.value));
            };
        };
        const reject = (reason)=>{
            // 失败后的一系列操作(状态的改变,成功回调的执行)
            if(this.state === "pending"){
                this.state = "rejected";
                this.reason = reason;
                this.onRejectedCallbacks.forEach(fn=>fn(this.reason));
            }
        };
        try{
            executor(resolve,reject);
        }catch(err){
            reject(err);
        }
    }
    then(onFulfilled,onRejected){

        // onFulfilled未传值或传的值不是function的时候
        // 自动把onFulfilled变成一个函数
        if(typeof onFulfilled !== "function"){
            onFulfilled = value => value;
        };

        //onRejected未传值或传的值不是function的时候
        //自动把onFulfilled变成一个函数,并抛出错误
        if(typeof onRejected !== "function"){
            onRejected =  reason => { throw reason }
        };

        if(this.state === "pending"){
            this.onFulfilledCallbacks.push(
                (value)=>{
                    setTimeout(()=>onFulfilled(value))
                }
            );
            this.onRejectedCallbacks.push(
                (reason)=>{
                    setTimeout(()=>onRejected(reason))
                }
            );

        };

        if(this.state === "fulfilled"){
            setTimeout(()=>onFulfilled(this.value));
        };

        if(this.state === "rejected"){
            setTimeout(()=>onRejected(this.reason));
        };
    }
};

new _Promise((resolve,reject)=>{
    setTimeout(()=>{
        resolve(3);
    });
})
.then(
    value=>console.log(value)
);
五、链式调用

这就太简单了,我们直接在 then 里面返回这个实例(return this)不就完了吗。就在将要收工的时候。测试:

new Promise((resolve,reject)=>{
    resolve(3);
})
.then(value=>console.log(value))
.then()
.then(
value=>console.log(value)
)
// 3 undefined

而我们手写的运行上面的代码输出3 3

Promises/A+规范:

执行态(Fulfilled)
处于执行态时,promise 需满足以下条件:

  • 不能迁移至其他任何状态
  • 必须拥有一个不可变的终值

依据上面的规范,我们手写的 Promise 必须保证(终值不可变)也就是只能在一个 then 里面接收。所以直接 return this 这个方法不可行。那我们就 return new _Promise() 一个 then 对应一个状态不就行了。然后把 onFulfilled,onRejected 放到新 new 出来的 _Promise 里面执行。

同时把 then 函数里面执行的结果赋值给 x。到这里就关键了:

  1. 返回的是 _Promise 实例,如果想打点 then,必须的改变状态 state,所以返回 _Promise 实例的时候就已经调用 resolve 或 reject。
  2. 连续打点 then 的时候,比如:
new _Promise((resolve,reject)=>resolve(3)).then().then(value => console.log("value",value))

第一个 then 里面没有参数,resolve 里面的内容传给了 第二个 then 里面的 value。所以本例第一个then 没有传参数,onFulfilled 函数是 value=>value,返回的是 resolve 里面的值。这时我们的 resolve(x)。

这时候代码完成如下:

class _Promise {
    constructor(executor){
        // 校验executor
        if(typeof executor !== "function"){
            throw new Error(`Promise resolver ${executor} is not a function!`);
        };

        this.value = undefined; //终值=>resolve的值
        this.reason = undefined;//拒因=>reject的值
        this.state = "pending";//状态

        this.onFulfilledCallbacks = [];// 成功回调
        this.onRejectedCallbacks = [];// 失败回调

        const resolve = (value)=>{
            // 成功后的一系列操作(状态的改变,成功回调的执行)
            if(this.state === "pending"){
                this.state = "fulfilled";
                this.value = value;
                this.onFulfilledCallbacks.forEach(fn=>fn(this.value));
            };
        };
        const reject = (reason)=>{
            // 失败后的一系列操作(状态的改变,成功回调的执行)
            if(this.state === "pending"){
                this.state = "rejected";
                this.reason = reason;
                this.onRejectedCallbacks.forEach(fn=>fn(this.reason));
            }
        };
        try{
            executor(resolve,reject);
        }catch(err){
            reject(err);
        }
    }
    then(onFulfilled,onRejected){

        // onFulfilled未传值或传的值不是function的时候
        // 自动把onFulfilled变成一个函数
        if(typeof onFulfilled !== "function"){
            onFulfilled = value => value;
        };

        //onRejected未传值或传的值不是function的时候
        //自动把onFulfilled变成一个函数,并抛出错误
        if(typeof onRejected !== "function"){
            onRejected =  reason => { throw reason }
        };

        const promise2 = new _Promise((resolve,reject)=>{
            if(this.state === "pending"){
                this.onFulfilledCallbacks.push(
                    (value)=>{
                        setTimeout(()=>{
                            const x = onFulfilled(value);
                            resolve(x);
                        })
                    }
                );
                this.onRejectedCallbacks.push(
                    (reason)=>{
                        setTimeout(()=>{
                            const x = onRejected(reason);
                            reject(x);
                        })
                    }
                );

            };

            if(this.state === "fulfilled"){
                setTimeout(()=>{
                    const x = onFulfilled(this.value);
                    resolve(x);
                });
            };

            if(this.state === "rejected"){
                setTimeout(()=>{
                    const x = onRejected(this.reason);
                    reject(x);
                });
            };
        });

        return promise2;
    }
};

new _Promise((resolve,reject)=>{
    resolve(3);
})
.then(
value => console.log("value",value)
)
.then(value => console.log("value",value))

输出结果:第一个 value 是 3 ,第二个 value 是 undefined,和 ES6 的 Promise 输出结果是一样的。

解决小问题:

  1. x === promise2

原生 ES6 测试下面代码:

let p = new Promise(resolve => {
    resolve(0);
}).then(data => p);

报错TypeError: Chaining cycle detected for promise ES6 的 Promise 会等待 p 的状态改变,而 p 一直在递归调用自己,所以报错。我们手写的没有这个功能。所以上面的代码在 _Promise 可以翻译为:如果 x === promise2,则是会造成循环引用,自己等待自己完成,则报错。

if(this.state === "fulfilled"){
    setTimeout(()=>{
        const x = onFulfilled(this.value);
        // resolvePromise函数,处理自己return的promise和默认的promise2的关系
        resolvePromise(promise2, x, resolve, reject);
    });
};

resolvePromise函数:

function resolvePromise(promise2, x, resolve, reject){
    // x 与 promise 相等
    if (promise2 === x) {
        return reject(new TypeError("Chaining cycle detected for promise"));
    }
    resolve(x);
}
  1. 返回值为 promise 时
new Promise((resolve,reject)=>{
    resolve(3);
})
.then(
value => {
    return new Promise((resolve,reject)=>{
        resolve(1)
    })
})
.then(value => console.log("value",value))

输出结果:value 1,ES6 会等待新的 Promise 执行完毕。但是 _Promise 会返回一个
_Promise 实例,我们需要在这里等待新的 _Promise 执行拿到它执行的结果。再次改写 resolvePromise:

// 当x为_Promise时
if (x instanceof _Promise) {
    x.then(
        value => {
            resolve(value);
        },
        reason => {
            reject(reason)
        }
    );
}

你以为完成了是吧,嘻嘻,当我们这样:

new Promise((resolve,reject)=>{
    resolve(3);
})
.then(
value => {
    return new Promise((resolve,reject)=>{
        resolve(new Promise((resolve,reject)=>{
            resolve(1)
        }))
    })
})
.then(value => console.log("value",value))

很明显了,_promise 会输出 _Promise 实例,所以我们的递归,在再次改动:

// 当x为_Promise时
if (x instanceof _Promise) {
    x.then(
        value => {
            resolvePromise(promise2, value, resolve, reject);
        },
        reason => {
            reject(reason)
        }
    );
}else{
    resolve(x)
}

到这里我们基本(因为没加防止多次调用,现在 reject 和 resolve 可同时调用,ES6中肯定是不能同时调用的)已经完成了手写 Promise ,只是 _Promise 的 resolvePromise 函数现在还不符合 Promises/A+规范,因为 Promises/A+规范对于 x 是这样要求的。


Promises/A+规范

所以真正符合 Promises/A+规范的 resolvePromise:

function resolvePromise(promise2, x, resolve, reject){
  // 循环引用报错
  if(x === promise2){
    // reject报错
    return reject(new TypeError('Chaining cycle detected for promise'));
  }
  // 防止多次调用
  let called;
  // x不是null 且x是对象或者函数
  if (x != null && (typeof x === 'object' || typeof x === 'function')) {
    try {
      // A+规定,声明then = x的then方法
      let then = x.then;
      // 如果then是函数,就默认是promise了
      if (typeof then === 'function') { 
        // 就让then执行 第一个参数是this   后面是成功的回调 和 失败的回调
        then.call(x, y => {
          // 成功和失败只能调用一个
          if (called) return;
          called = true;
          // resolve的结果依旧是promise 那就继续解析
          resolvePromise(promise2, y, resolve, reject);
        }, err => {
          // 成功和失败只能调用一个
          if (called) return;
          called = true;
          reject(err);// 失败了就失败了
        })
      } else {
        resolve(x); // 直接成功即可
      }
    } catch (e) {
      // 也属于失败
      if (called) return;
      called = true;
      // 取then出错了那就不要在继续执行了
      reject(e); 
    }
  } else {
    resolve(x);
  }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容