• 技术文章 >web前端 >js教程

    实现Promise的详细步骤

    不言不言2019-03-29 09:13:57转载1290

    本篇文章给大家带来的内容是关于实现Promise的详细步骤,有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助。

    1、constructor

    首先我们都知道Promise 有三个状态,为了方便我们把它定义成常量

    const PENDING = 'pending';
    const FULFILLED = 'fulfilled';
    const REJECTED = 'rejected';

    接下来我们来定义一个类

    class MyPromise {
        constructor(executor) {
            //控制状态,使用了一次之后,接下来的都不被使用
            this.state = PENDING;
            this.value = null;
            this.reason = null;
            
            // 定义resolve函数
            const resolve = value => {
                if (this.state === PENDING) {
                    this.value = value;
                    this.state = FULFILLED;
                }
            }
            
            // 定义reject函数
            const reject = value => {
                if (this.state === PENDING) {
                    this.reason = value;
                    this.state = REJECTED;
                }
            }
            
            // executor方法可能会抛出异常,需要捕获
            try {
                 // 将resolve和reject函数给使用者  
                executor(resolve, reject);
            } catch (error) {
                // 如果在函数中抛出异常则将它注入reject中
                reject(error);
            }
        }
    }

    到这基本比较好理解我简单说明一下

    executor:这是实例Promise对象时在构造器中传入的参数,一般是一个function(resolve,reject){}

    state:`Promise的状态,一开始是默认的pendding状态,每当调用道resolve和reject方法时,就会改变其值,在后面的then`方法中会用到

    value:resolve回调成功后,调用resolve方法里面的参数值

    reason:reject回调成功后,调用reject方法里面的参数值

    resolve:声明resolve方法在构造器内,通过传入的executor方法传入其中,用以给使用者回调

    reject:声明reject方法在构造器内,通过传入的executor方法传入其中,用以给使用者回调

    2、then

    then就是将Promise中的resolve或者reject的结果拿到,那么我们就能知道这里的then方法需要两个参数,成功回调和失败回调,上代码!

    then(onFulfilled, onRejected) {
        if (this.state === FULFILLED) {
            onFulfilled(this.value)
        }
        if (this.state === REJECTED) {
            onRejected(this.reason)
        }
    }

    我们来简单的运行一下测试代码

    const mp = new MyPromise((resolve, reject)=> {
        resolve('******* i love you *******');
    })
    mp.then((suc)=> {
    console.log(11111, suc);
    }, (err)=> {
    console.log('****** 你不爱我了*******', err)
    })
    
    // 11111 '******* i love you *******'

    这样看着好像没有问题,那么我们来试试异步函数呢?

    const mp = new MyPromise((resolve, reject)=> {
        setTimeout(()=> {
            resolve('******* i love you *******');
        }, 0)
    })
    mp.then((suc)=> {
    console.log(11111, suc);
    }, (err)=> {
    console.log('****** 你不爱我了*******', err)
    })

    我们会发现什么也没有打印,哪里出问题了呢?原来是由于异步的原因,当我们执行到then的时候this. state的值还没发生改变,所以then里面的判断就失效了。那么我们该怎么解决呢?

    这就要说回经典得callback 了。来上源码

    // 存放成功回调的函数
    this.onFulfilledCallbacks = [];
    // 存放失败回调的函数
    this.onRejectedCallbacks = [];
    
    const resolve = value => {
        if (this.state === PENDING) {
            this.value = value;
            this.state = FULFILLED;
            this.onFulfilledCallbacks.map(fn => fn());
        }
    }
    
    const reject = value => {
        if (this.state === PENDING) {
            this.value = value;
            this.reason = REJECTED;
            this.onRejectedCallbacks.map(fn => fn());
        }
    }

    在then里面添加

    then(onFulfilled, onRejected) {
        // ... 
        if(this.state === PENDING) {
            this.onFulfilledCallbacks.push(()=> {
                onFulfilled(this.value);
            });
            this.onRejectedCallbacks.push(()=> {
                onRejected(this.value);
            })
        }
    }

    好了,到这异步的问题解决了,我们再来执行一下刚才的测试代码。结果就出来了。到这我们还缺什么呢?

    链式调用

    当我们不传参数时应当什么运行

    这二个的思路也都很简单,链式调用也就是说我们再返回一个promise的实例就好了。而不传参则就是默认值的问题了。下面来看源码

    then(onFulfilled, onRejected) {
        let self = this;
        let promise2 = null;
        //解决onFulfilled,onRejected没有传值的问题
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : y => y
        //因为错误的值要让后面访问到,所以这里也要跑出个错误,不然会在之后then的resolve中捕获
        onRejected = typeof onRejected === 'function' ? onRejected : err => {
            throw err;
        }
    
        promise2 = new MyPromise((resolve, reject) => {
            if (self.state === PENDING) {
                console.log('then PENDING')
                self.onFulfilledCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            let x = onFulfilled(self.value);
                        console.log(333333, x, typeof x);
                            self.resolvePromise(promise2, x, resolve, reject);
                        } catch (reason) {
                            reject(reason);
                        }
                    }, 0)
    
                });
                self.onRejectedCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            let x = onRejected(self.reason);
                            self.resolvePromise(promise2, x, resolve, reject);
                        } catch (reason) {
                            reject(reason);
                        }
                    }, 0);
                });
            }
    
            if (self.state === FULFILLED) {
                console.log('then FULFILLED')
                setTimeout(() => {
                    try {
                        let x = onFulfilled(self.value);
                        self.resolvePromise(promise2, x, resolve, reject);
                    } catch (reason) {
                        reject(reason);
                    }
                }, 0);
            }
    
            if (self.state === REJECTED) {
                console.log('then REJECTED')
                setTimeout(() => {
                    try {
                        let x = onRejected(self.reason);
                        self.resolvePromise(promise2, x, resolve, reject);
                    } catch (reason) {
                        reject(reason);
                    }
                })
            }
        });
    
        return promise2;
    }

    为什么外面要包一层setTimeout?:因为Promise本身是一个异步方法,属于微任务一列,必须得在执行栈执行完了在去取他的值,所以所有的返回值都得包一层异步setTimeout。

    resolvePromise是什么?:这其实是官方Promise/A+的需求。因为你的then可以返回任何职,当然包括Promise对象,而如果是Promise对象,我们就需要将他拆解,直到它不是一个Promise对象,取其中的值。

    3、resolvePromise

    我们直接看代码

    resolvePromise(promise2, x, resolve, reject) {
        let self = this;
        let called = false; // called 防止多次调用
        //因为promise2是上一个promise.then后的返回结果,所以如果相同,会导致下面的.then会是同一个promise2,一直都是,没有尽头
        //相当于promise.then之后return了自己,因为then会等待return后的promise,导致自己等待自己,一直处于等待
        if (promise2 === x) {
            return reject(new TypeError('循环引用'));
        }
        //如果x不是null,是对象或者方法
        if (x !== null && (Object.prototype.toString.call(x) === '[object Object]' || Object.prototype.toString.call(x) === '[object Function]')) {
            // x是对象或者函数
            try {
                let then = x.then;
    
                if (typeof then === 'function') {
                    then.call(x, (y) => {
                        // 别人的Promise的then方法可能设置了getter等,使用called防止多次调用then方法
                        if (called) return;
                        called = true;
                        // 成功值y有可能还是promise或者是具有then方法等,再次resolvePromise,直到成功值为基本类型或者非thenable
                        self.resolvePromise(promise2, y, resolve, reject);
                    }, (reason) => {
                        if (called) return;
                        called = true;
                        reject(reason);
                    });
                } else {
                    if (called) return;
                    called = true;
                    resolve(x);
                }
            } catch (reason) {
                if (called) return;
                called = true;
                reject(reason);
            }
        } else {
            // x是普通值,直接resolve
            resolve(x);
        }
    }

    为什么要在一开始判断promise2和x?:首先在Promise/A+中写了需要判断这两者如果相等,需要抛出异常,我就来解释一下为什么,如果这两者相等,我们可以看下下面的例子,第一次p2是p1.then出来的结果是个Promise对象,这个Promise对象在被创建的时候调用了resolvePromise(promise2,x,resolve,reject)函数,又因为x等于其本身,是个Promise,就需要then方法递归它,直到他不是Promise对象,但是x(p2)的结果还在等待,他却想执行自己的then方法,就会导致等待。

    为什么要递归去调用resolvePromise函数?:相信细心的人已经发现了,我这里使用了递归调用法,首先这是Promise/A+中要求的,其次是业务场景的需求,当我们碰到那种Promise的resolve里的Promise的resolve里又包了一个Promise的话,就需要递归取值,直到x不是Promise对象。

    4、catch

    //catch方法
    catch(onRejected){
      return this.then(null,onRejected)
    }

    5、finally

    finally方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的。

    finally(fn) {
        return this.then(value => {
            fn();
            return value;
        }, reason => {
            fn();
            throw reason;
        });
    };

    6、resolve/reject

    大家一定都看到过Promise.resolve()、Promise.reject()这两种用法,它们的作用其实就是返回一个Promise对象,我们来实现一下。

    static resolve(val) {
        return new MyPromise((resolve, reject) => {
            resolve(val)
        })
    }
    //reject方法
    static reject(val) {
        return new MyPromise((resolve, reject) => {
            reject(val)
        })
    }

    7、all

    all方法可以说是Promise中很常用的方法了,它的作用就是将一个数组的Promise对象放在其中,当全部resolve的时候就会执行then方法,当有一个reject的时候就会执行catch,并且他们的结果也是按着数组中的顺序来排放的,那么我们来实现一下。

    static all(promiseArr) {
        return new MyPromise((resolve, reject) => {
            let result = [];
    
            promiseArr.forEach((promise, index) => {
                promise.then((value) => {
                    result[index] = value;
    
                    if (result.length === promiseArr.length) {
                        resolve(result);
                    }
                }, reject);
            });
        });
    }

    8、race

    race方法虽然不常用,但是在Promise方法中也是一个能用得上的方法,它的作用是将一个Promise数组放入race中,哪个先执行完,race就直接执行完,并从then中取值。我们来实现一下吧。

    static race(promiseArr) {
        return new MyPromise((resolve, reject) => {
            promiseArr.forEach(promise => {
                promise.then((value) => {
                    resolve(value);
                }, reject);
            });
        });
    }

    9、deferred

    static deferred() {
        let dfd = {};
        dfd.promies = new MyPromise((resolve, reject) => {
            dfd.resolve = resolve;
            dfd.rfeject = reject;
        });
        return dfd;
    };

    什么作用呢?看下面代码你就知道了

    let fs = require('fs')
    let MyPromise = require('./MyPromise')
    //Promise上的语法糖,为了防止嵌套,方便调用
    //坏处 错误处理不方便
    function read(){
      let defer = MyPromise.defer()
      fs.readFile('./1.txt','utf8',(err,data)=>{
        if(err)defer.reject(err)
        defer.resolve(data)
      })
      return defer.Promise
    }

    10、测试

    const mp1 = MyPromise.resolve(1);
    const mp2 = MyPromise.resolve(2);
    const mp3 = MyPromise.resolve(3);
    const mp4 = MyPromise.reject(4);
    
    MyPromise.all([mp1, mp2, mp3]).then(x => {
        console.log(x);
    }, (err) => {
        console.log('err1', err);
    })
    MyPromise.race([mp1, mp4, mp2, mp3]).then(x => {
        console.log(x);
    }, (err) => {
        console.log('err2', err);
    })
    
    var mp = new MyPromise((resolve, reject) => {
        console.log(11111);
        setTimeout(() => {
            resolve(22222);
            console.log(3333);
        }, 1000);
    });
    mp.then(x => {
        console.log(x);
    }, (err) => {
        console.log('err2', err);
    })
    //11111
    //[ 1, 2, 3 ]
    //1
    //3333
    //22222

    完整源码请查看

    本篇文章到这里就已经全部结束了,更多其他精彩内容可以关注PHP中文网的的JavaScript教程视频栏目!!!

    以上就是实现Promise的详细步骤的详细内容,更多请关注php中文网其它相关文章!

    声明:本文转载于:segmentfault,如有侵犯,请联系admin@php.cn删除
    专题推荐:promise es6 javascript
    上一篇:jQuery中hasClass()的意思及用法详解 下一篇:nodejs模块的简单理解(附示例)
    线上培训班

    相关文章推荐

    • Promise实现思路的深入分析(代码示例)• 如何使用JavaScript中的promise对象• JavaScript实现promise的方法(代码示例)• JavaScript中promise和setTimeout执行顺序的问题(代码示例)

    全部评论我要评论

  • 取消发布评论发送
  • 1/1

    PHP中文网