最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 手写Promise,通过Promise/A+的872个测试

    正文概述 掘金(欧怼怼)   2021-04-19   561

    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+测试

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

    起源地下载网 » 手写Promise,通过Promise/A+的872个测试

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元