关于javascript:手写Promise通过PromiseA的872个测试

32次阅读

共计 11692 个字符,预计需要花费 30 分钟才能阅读完成。

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-a…

Promise/A+测试工具: https://github.com/promises-a…

装置 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)

正文完
 0