手写Promise,通过Promise/A+的872个测试

github

博客

Promise的声明

当我们使用Promise的时候,通常都是new Promise((resolve, reject) => {})

因此我们可以看出:

  • Promise是一个类;
  • Promise类的构造函数的第一个参数是函数,这个函数叫处理器函数(executor function);
  • 而在处理器函数中,有了两个参数:resolvereject
    • 当异步任务顺利完成且返回结果值的时候,我们会调用resolve函数;
    • 当异步任务失败且返回失败原因(通常是一个错误对象)时,会调用reject函数。

因此,我们可以初步声明一下Promise类。

class Promise {
    /**
     * 构造器
     * @returns {Promise<object>}
     * @param executor<function>: executor有两个参数:resolve和reject
     */
    constructor(executor) {
        // resolve 成功
        const resolve = () => {};

        // reject 失败
        const reject = () => {};

        // 执行 executor
        executor(resolve,reject);
    }
}

实现Promise的基本状态

Promise存在着三种状态:pending(等待态)、fulfilled(成功态)和rejected(失败态):

  • Promise的初始状态是pending状态;
  • pending状态可以转换为fulfilled状态和rejected状态;
  • fulfilled状态不可以转为其他状态,且必须有一个不可改变的值(value);
  • rejected状态不可以转为其他状态,且必须有一个不可改变的原因(reason);
  • 当在处理器函数中调用resolve函数并传入参数value,则状态改变为fulfilled,且不可以改变;
  • 当在处理器函数中调用reject函数并传入参数reason,则状态改变为rejected,且不可以改变;
  • 若处理器函数执行中报错,直接执行reject函数。

因此,我们需要在Promise类中设置三个变量:state(状态变量),value(成功值的变量)和reason(失败原因的变量),然后在resolve函数、reject函数以及执行executor函数报错的时候改变state的值。

class Promise {
    constructor(executor) {
        // 初始化状态
        this.state = 'pending';
        // 成功的值
        this.value = undefined;
        // 失败的原因
        this.reason = undefined;
        
        /**
         * resolve 成功函数
         * @param value<any>: 成功的值
         */
        const resolve = (value) => {
            // 只能在状态为pending的时候执行
            if(this.state === 'pending'){
                // resolve调用后,state转化为fulfilled
                this.state = 'fulfilled';
                // 存储value
                this.value = value;
            }
        };

        /**
         * reject 失败函数
         * @param reason<any>: 失败的原因
         */
        const reject = (reason) => {
            // 只能在状态为pending的时候执行
            if(this.state === 'pending'){
                // resolve调用后,state转化为rejected
                this.state = 'rejected';
                // 存储reason
                this.reason = reason;
            }
        };

        // 如果executor执行报错,直接执行reject()
        try {
            executor(resolve,reject);
        }catch (e){
            reject(e);
        }
    }
}

then方法

Promise有一个then方法,而该方法中有两个参数:onFulfilledonRejected

  • 这两个参数都是一个函数,且会返回一个结果值;
  • 当状态为fulfilled,只执行onFulfilled,传入this.value
  • 当状态为rejected,只执行onRejected,传入this.reason

因此我们可以来实现一下then方法。

class Promise {
   constructor(executor) {...}

    /**
     * then 方法
     * @param onFulfilled<function>: 状态为fulfilled时调用
     * @param onRejected<function>: 状态为rejected时调用
     */
    then(onFulfilled, onRejected) {
        // 状态为fulfilled的时候,执行onFulfilled,并传入this.value
        if(this.state === 'fulfilled'){
            /**
             * onFulfilled 方法
             * @param value<function>: 成功的结果
             */
            onFulfilled(this.value)
        }

        // 状态为rejected的时候,onRejected,并传入this.reason
        if(this.state === 'rejected'){
            /**
             * onRejected 方法
             * @param reason<function>: 失败的原因
             */
            onRejected(this.reason)
        }
    }
}

异步实现

Promise实际上一个异步操作:

  • resolve()是在setTimeout内执行的;
  • 当执行then()函数时,如果状态是pending时,我们需要等待状态结束后,才继续执行,因此此时我们需要将then()的两个参数onFulfilledonRejected存起来;
  • 因为一个Promise实例可以调用多次then(),因此我们需要将onFulfilledonRejected各种用数组存起来。

因此我们可以借着完善代码:

class Promise {
    /**
     * 构造器
     * @returns {Promise<object>}
     * @param executor<function>: executor有两个参数:resolve和reject
     */
    constructor(executor) {
        this.state = 'pending';
        this.value = undefined;
        this.reason = undefined;
        // 存储onFulfilled的数组
        this.onResolvedCallbacks = [];
        // 存储onRejected的数组
        this.onRejectedCallbacks = [];

        const resolve = (value) => {
            if (this.state === 'pending') {
                this.state = 'fulfilled';
                this.value = value;
                // 一旦resolve执行,调用onResolvedCallbacks数组的函数
                this.onResolvedCallbacks.forEach(fn => fn());
            }
        };

        const reject = (reason) => {
            if (this.state === 'pending') {
                this.state = 'rejected';
                this.reason = reason;
                // 一旦reject执行,调用onRejectedCallbacks数组的函数
                this.onRejectedCallbacks.forEach(fn=>fn());
            }
        };

        try {
            executor(resolve, reject);
        } catch (e) {
            reject(e);
        }
    }

    then(onFulfilled, onRejected) {
        if (this.state === 'fulfilled') {
            onFulfilled(this.value)
        }

  
        if (this.state === 'rejected') {
            onRejected(this.reason)
        }

        // 状态为pending的时候,将onFulfilled、onRejected存入数组
        if (this.state === 'pending') {
            this.onResolvedCallbacks.push(() => {
                onFulfilled(this.value)
            })
            this.onRejectedCallbacks.push(() => {
                onRejected(this.reason)
            })
        }
    }
}

实现链式调用

我们常常会像下面代码一样使用Promise

new Promise()
    .then()
    .then()
    .then()

这种方法叫做链式调用,通常是用来解决回调地狱(Callback Hell)的,就如下的代码:

fs.readdir(source, function (err, files) {
  if (err) {
    console.log('Error finding files: ' + err)
  } else {
    files.forEach(function (filename, fileIndex) {
      console.log(filename)
      gm(source + filename).size(function (err, values) {
        if (err) {
          console.log('Error identifying file size: ' + err)
        } else {
          console.log(filename + ' : ' + values)
          aspect = (values.width / values.height)
          widths.forEach(function (width, widthIndex) {
            height = Math.round(width / aspect)
            console.log('resizing ' + filename + 'to ' + height + 'x' + height)
            this.resize(width, height).write(dest + 'w' + width + '_' + filename, function(err) {
              if (err) console.log('Error writing file: ' + err)
            })
          }.bind(this))
        }
      })
    })
  }
})

为了实现链式调用,我们需要满足一下几点:

  • 我们需要在then()返回一个新的Promise实例;
  • 如果上一个then()返回了一个值,则这个值就是onFulfilled()或者onRejected()的值,我们需要把这个值传递到下一个then()中。

而对于上一个then()的返回值,我们需要对齐进行一定的处理,因此封装一个resolvePromise()的方法去进行判断处理;

接下来我们对then()方法进行修改:

class Promise {
    constructor(executor) { ... }

    /**
     * then 方法
     * @returns {Promise<object>}
     * @param onFulfilled<function>: 状态为fulfilled时调用
     * @param onRejected<function>: 状态为rejected时调用
     */
    then(onFulfilled, onRejected) {
        // 返回一个新的Promise实例
        const newPromise = new Promise((resolve, reject) => {

            if (this.state === 'fulfilled') {
                const x = onFulfilled(this.value)

                // 对返回值进行处理 
                resolvePromise(newPromise, x, resolve, reject);
            }

            if (this.state === 'rejected') {
                const x = onRejected(this.reason);

                // 对返回值进行处理 
                resolvePromise(x, resolve, reject);
            }

            if (this.state === 'pending') {
                this.onResolvedCallbacks.push(() => {
                    const x = onFulfilled(this.value);

                    // 对返回值进行处理 
                    resolvePromise(newPromise, x, resolve, reject);
                })
                this.onRejectedCallbacks.push(() => {
                    const x = onRejected(this.reason);

                    // 对返回值进行处理 
                    resolvePromise(newPromise, x, resolve, reject);
                })
            }
        });
      
        return newPromise;
    }
}

function resolvePromise() {}

完成resolvePromise函数

对于上一个then()的返回值,我们用x变量存起来,然后需要对它进行一个处理:

  • 判断x是不是Promise实例;
    • 如果是Promise实例,则取它的结果,作为新的Promise实例成功的结果;
    • 如果是普通值,直接作为Promise成功的结果;

然后我们处理返回值后,需要利用newPromiseresolvereject方法将结果返回。

这里我们还需要注意一个地方,就是x等于newPromise的话,这时会造成循环引用,导致死循环。

let p = new Promise(resolve => {
  resolve(0);
});
const p2 = p.then(data => {
  // 循环引用,自己等待自己完成,导致死循环
  return p2;
})

因此,resolvePromise函数需要4个参数,即newPromisexresolvereject

所以我们来实现一下resolvePromise函数:

/**
 * resolvePromise 方法
 * @param newPromise<object>: 新的Promise实例
 * @param x<any>: 上一个then()的返回值
 * @param resolve<function>:Promise实例的resolve方法
 * @param reject<function>:Promise实例的reject方法
 */
function resolvePromise(newPromise, x, resolve, reject) {
    // 循环引用报错
    if(x === newPromise){
        // reject报错
        return reject(new TypeError('Chaining cycle detected for promise'));
    }
    // 防止多次调用
    let called;
    if (x != null && (typeof x === 'object' || typeof x === 'function')) {
        try {
            let then = x.then;
            // x 为Promise实例
            if (typeof then === 'function') {
                // 使用call执行then(),call的第一个参数是this,后续即then()的参数,即第二个是成功的回调方法,第三个为失败的回调函数
                then.call(x, y => {
                    // 成功和失败只能调用一个
                    if(called)return;
                    called = true;
                    // resolve 的结果依旧是promise实例,那就继续解析
                    resolvePromise(newPromise, y, resolve, reject);
                }, err => {
                    // 成功和失败只能调用一个
                    if(called)return;
                    called = true;
                    // 失败了就直接返回reject报错
                    reject(err);
                })
            } else {
                // x 为普通的对象或方法,直接返回
                resolve(x);
            }
        } catch (e) {
            if(called)return;
            called = true;
            reject(e);
        }
    } else {
        // x 为普通的值,直接返回
        resolve(x);
    }
}

onFulfilledonRejected

关于then()的两个参数——onFulfilledonRejected

  • 它们都是可选参数,而且它们都是函数,如果不是函数的话,就会被忽略掉;
    • 如果onFulfilled不是一个函数,就将它直接替换成函数value => value
    • 如果onRejected不是一个函数,就将它直接替换成函数err => {throw err};
class Promise {
    constructor(executor) { ... }

    then(onFulfilled, onRejected) {
        // onFulfilled如果不是函数,就忽略onFulfilled,直接返回value
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
        // onRejected如果不是函数,就忽略onRejected,直接抛出错误
        onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
        
      ...
    }
}

其次,onFulfilledonRejected是不能同步被调用的,必须异步调用。因此我们就用setTimeout解决一步问题。

class Promise {
    constructor(executor) { ... }

    then(onFulfilled, onRejected) {
        // onFulfilled如果不是函数,就忽略onFulfilled,直接返回value
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
        // onRejected如果不是函数,就忽略onRejected,直接抛出错误
        onRejected = typeof onRejected === 'function' ? onRejected : err => {
            throw err
        };

        return new Promise((resolve, reject) => {
            if (this.state === 'fulfilled') {
                // 异步调用
                setTimeout(() => {
                    try {
                        const x = onFulfilled(this.value)
                        resolvePromise(x, resolve, reject);
                    }catch (e){
                        reject(e)
                    }
                })
            }

            if (this.state === 'rejected') {
                // 异步调用
                setTimeout(() => {
                    try{
                        const x = onRejected(this.reason);

                        resolvePromise(x, resolve, reject);
                    }catch (e){
                        reject(e)
                    }
                })
            }
          
            if (this.state === 'pending') {
                this.onResolvedCallbacks.push(() => {
                  // 异步调用
                    setTimeout(() => {
                        try {
                            const x = onFulfilled(this.value);
                            resolvePromise(x, resolve, reject);
                        }catch (e){
                            reject(e)
                        }
                    })
                })
                this.onRejectedCallbacks.push(() => {
                  // 异步调用
                    setTimeout(() => {
                        try {
                            const x = onRejected(this.reason);
                            resolvePromise(x, resolve, reject);
                        }catch (e){
                            reject(e)
                        }
                    })
                })
            }
        });
    }
}

实现Promise的其他方法

Promise.all()

Promise.all()方法接收一个promiseiterable类型的输入,包括ArrayMapSet。然后返回一个Promise实例,该实例回调返回的结果是一个数组,包含输入所有promise的回调结果。

但只要任何一个输入的promisereject回调执行或者输入不合法的promise,就会立马抛出错误。

/**
 * Promise.all 方法
 * @returns {Promise<object>}
 * @param promises<iterable>: 一个promise的iterable类型输入
 */
Promise.all = function (promises) {
    let arr = [];

    return new Promise((resolve, reject) => {
       if (!promises.length) resolve([]);
        // 遍历promises
        for(const promise of promises) {
            promise.then(res => {
                arr.push(res);
                if(arr.length === promises.length){
                    resolve(arr);
                }
            }, reject)
        }
    })
}

Promise.allSettled()

Promise.allSettled()其实跟Promise.all()很像,同样是接收一个promiseiterable类型的输入,但返回的是一个给定的promise已经完成后的promise,并带有一个对象数组,每个对象标识着对应的promise结果。

const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve, reject) => setTimeout(reject, 100, 'foo'));
const promises = [promise1, promise2];

Promise.allSettled(promises).
  then((results) => console.log(results));
// > Array [Object { status: "fulfilled", value: 3 }, Object { status: "rejected", reason: "foo" }]

实现:

/**
 * Promise.allSettled 方法
 * @returns {Promise<object>}
 * @param promises<iterable>: 一个promise的iterable类型输入
 */
Promise.allSettled = function (promises) {
    let arr = [];

    return new Promise((resolve, reject) => {
        try {
            const processData = (data) => {
                arr.push(data);
                if(arr.length === promises.length){
                    resolve(arr);
                }
            }

             if (!promises.length) resolve([]);
            // 遍历promises
            for(const promise of promises) {
                promise.then(res => {
                    processData({state:'fulfilled', value: res})
                }, err => {
                    processData({state:'rejected', reason: err})
                })
            }
        }catch (e){
            reject(e)
        }
    })
}

Promise.any()

Promise.any()Promise.all()Promise.allSettled()一样,同样是接收一个promiseiterable类型的输入。但只要其中的一个promise成功,就返回那个已经成功的promise,但如果没有一个promise成功,就返回一个失败的promise`。

/**
 * Promise.any 方法
 * @returns {Promise<object>}
 * @param promises<iterable>: 一个promise的iterable类型输入
 */
Promise.any = function (promises) {
    return new Promise((resolve, reject) => {
        // 如果传入的参数是一个空的可迭代对象,则返回一个 已失败(already rejected) 状态的 Promise
        if (!promises.length) reject();
        // 如果传入的参数不包含任何 promise,则返回一个 异步完成 (asynchronously resolved)的 Promise。
        if (typeof promises[Symbol.iterator] !== 'function' ||
            promises === null ||
            typeof promises === 'string') {
            resolve()
        }

        let i = 0;
        // 遍历promises
        for (const promise of promises) {
            promise.then(res => {
                i++;
                resolve(res);
            }, err => {
                i++;
                if (i === promises.length) {
                    reject(err);
                }
            })
        }
    })
}

Promise.race()

Promise.race(),同样是接收一个promiseiterable类型的输入。一旦迭代器中的某个promise完成了,不管是成功还是失败,就会返回这个promise

/**
 * Promise.race 方法
 * @returns {Promise<object>}
 * @param promises<iterable>: 一个promise的iterable类型输入
 */
Promise.race = function (promises) {
    return new Promise((resolve, reject) => {
        for (const promise of promises) {
            promise.then(resolve, reject)
        }
    })
}

Promise.reject()Promise.resolve()

Promise.reject()方法返回一个带有拒绝原因的Promise对象;Promise.resolve()方法返回一个以定值解析后的Promise对象。

/**
 * Promise.reject 方法
 * @returns {Promise<object>}
 * @param val<any>
 */
Promise.reject = function (val) {
    return new Promise(reject => reject(val))
}

/**
 * Promise.resolve 方法
 * @returns {Promise<object>}
 * @param val<any>
 */
Promise.resolve = function (val) {
    return new Promise(resolve => resolve(val))
}

catch()finally()

catch()方法是用来处理失败的情况,它传入一个处理函数,然后返回一个promise实例。实际上它是then()的语法糖,只接受rejected态的数据。

finally()是在promise结束时,无论结果是fufilled还是rejected,都会执行指定的回调函数。同样也返回一个promise实例。

class Promise {
    constructor(executor) { ... }

    then(onFulfilled, onRejected) { ... }

    /**
     * catch 方法
     * @returns {Promise<object>}
     * @param callback<function>: 处理函数
     */
    catch(callback) {
        return this.then(null, callback);
    }

    /**
     * finally 方法
     * @returns {Promise<object>}
     * @param callback<function>: 处理函数
     */
    finally(callback) {
        return this.then(res => {
            return Promise.resolve(callback()).then(() => res)
        }, err => {
            return Promise.reject(callback()).then(() => {
                throw err
            })
        })
    }
}

Promise/A+测试

Promise/A+规范: https://github.com/promises-aplus/promises-spec

Promise/A+测试工具: https://github.com/promises-aplus/promises-tests

安装promises-aplus-tests插件。

yarn add promises-aplus-tests

Promise.js后面插入下列代码。

// 测试
Promise.defer = Promise.deferred = function () {
    let dfd = {}
    dfd.promise = new Promise((resolve,reject)=>{
        dfd.resolve = resolve;
        dfd.reject = reject;
    });
    return dfd;
}
module.exports = Promise;

然后输入命令行进行测试。

promises-aplus-tests Promise.js

结果:

872 passing (18s)
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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

推荐阅读更多精彩内容