Manufacturers face questions handwritten Promise Source

Handwritten Promise source almost every interview requirements will be a giant test center will, every time I hear the source, there is always a feeling on top of people, because they never materialized, always feel this thing is very difficult to achieve, and then to the nearest quit to prepare written from scratch learned the basics, handwritten call source, apply source, Promise source, feeling quite interesting, is not so difficult to think about. The short answer is a js achieve. As long as people can advantage js-based hand out, so do not be a "source" gave the word scared. Yourself to achieve it again, dozens of times better than watching the effect of others.

This article from the practical point of thinking how Promise is an implementation process will start start with a simple application, and then little by little to perfect the entire code.

First to a simple example:

// beginning waiting state, Pending 
the let Promise = new new Promise ((Resolve, Reject) => { IF (ERR) return Reject (ERR) // failed state fails to return failure information 
        Resolve (Data) // successful return data state successfully 
}) 
// state changes the call 
promise.then (data => { // successful call to 
    the console.log (data) 
}, ERR => {              // failed calls 
    the console.log (ERR) 
})

This example is a promise, it has three states, Pending (waiting state), fullfilled (success state), when Rejected (failure state), Resolve is called, a state of wait states into successful state, reject as a failure, the state wait for the state to failed states.

When the state changes of the time will then perform the method, if it is successful on the output value of success, failure to return if the reason for failure. But the two of them can be called only one that is impossible to have a successful case of failure.

According to this example, we can implement a version of the simple functions.

First Edition: define the entire process, synchronization functions:

This version of our main implementation flow can go through, to perform synchronization tasks

MyPromise {class 
    constructor (executorCallback) { // callback executorCallback execute 
        the let = _this the this ; 
        _this.status = 'Pending'; // recording state 
        _this.value = ''; // record data returned 
        function resolveFn (Result) {
             IF (_this.status === 'pending') { // state from pending to be successful only 
                _this.status = 'resolved' 
                _this.value = Result 
            } 
        } 
        function rejectFn (reason) {
             IF (= _this.status == 'Pending') { //From the pending state can only be a failure 
                _this.status = 'Rejected' 
                _this.value = reason 
            } 
        } 
     executorCallback (resolveFn, rejectFn) 
    } 
    // determine the status of success or failure, the successful implementation onFullfiled, failed execution onRejected 
    the then (onFullfiled, onRejected) {
         IF ( the this .status === 'resolved' ) { 
            onFullfiled ( the this .data) 
        } 
        IF ( the this .status === 'Rejected' ) { 
            onRejected ( the this .err) 
        } 
    } 
}
1.定义_this保证每次都能获取到正确的this
2.定义一个常量status用来记录promise的状态
3.定义一个常量value用来保存执行结果的返回值
4.executorCallback是立即执行函数,需要我们手动传入两个参数,这两个参数代表成功和失败时候调用,需要我们在内部定义好。
5.resolveFn()和rejectedFn()函数,在执行前先判断一下状态,如果状态为pending就执行,并且让状态变为相应的成功态或者失败态,这样每次就只能执行一个,要么是resolveFn(),
要么是rejectedFn(),并且把相应的返回值赋值给value。
6.状态改变了之后就会调用Promise.then()方法,如果状态是成功态就执行onFullfilled(),如果状态是失败态,就执行onRejected()
到此基本流程通了,我们写个小案例测一下
可以看到,虽然我写了两个函数,但是它只执行了第一个,就说明当状态pending变为成功态之后不会再去执行失败的函数,同理当状态变为失败态之后也不会再去执行成功的函数,
现在虽然实现了可以执行同步任务,但是对于异步任务还是执行不了。例如

 可以看到控制台没有输出任何东西,现在我们就来解决一下如何实现异步任务。

第二版:实现异步功能

我们先来分析一下上一版为什么实现不了异步功能,当代码执行到setTimeout时,不会立即执行里面的函数,而是先放到一个异步调用栈里面,等到同步代码执行完了在执行里面的resolveFn函数,这个时候then已经执行完了,不会再执行,所以就不会输出任何东西,我们可以在resolveFn执行之前就将then的回调函数先保存起来,等到resolveFn执行的时候再去一个一个执行这些回调函数,这个时候就可以实现异步功能。

在原来的基础上修改

class myPromise{
    constructor(executorCallback){
        var _this = this
        _this.status = 'pending'
        _this.value 
        _this.onFullfilledCallback  = [] //存放成功时的回调函数
        _this.onRejectedCallback = [] //存放时的失败的回调函数
        function resolveFn (result) {
            let timer = setTimeout( () =>{ //异步任务
                clearTimeout(timer)
                // console.log('chengg')
                if(_this.status === 'pending'){
                    _this.status = 'resolved'
                    _this.value = result
                    _this.onFullfilledCallback .forEach(item =>item(_this.value));
                }
            })
        }
        function rejectFn (err) {
            let timer = setTimeout( () =>{ //异步任务
                clearTimeout(timer)
                if(_this.pending === 'pending'){
                    _this.status = 'rejected'
                    _this.value = err
                    _this.onRejectedCallback.forEach(item =>item(_this.value))
                }
            })
        }
        executorCallback(resolveFn,rejectFn)
    }
    
    then(onFullfiled,onRejected){
        if(this.status === 'pending') {
            this.onFullfilledCallback .push(onFullfiled)
            this.onRejectedCallback.push(onRejected)
        }
    }
}

我将修改了的部分用红色字体标出。

1.onResolvedCallback和onRejectedCallback用来存放成功和失败时候的回调函数,想想为什么是一个数组呢?我们前面分析过,第一个原因是同一个Promise实例可以调用多次then,
需要把这些方法都放在同一个数组里,例如
let p1 = new Promise( (resolve,reject) =>{
    setTimeout(function(){
        resolve('ok')
    },1000)
})
p1.then(result =>{
    console.log('result1:'+result)
},reason =>{
    console.log(reason)
})
p1.then(result =>{
    console.log('result2:'+result)
},reason =>{
    console.log(reason)
})

 第二个原因是当立即执行完 Promise 时,让它的状态还是pending的时候,应该把 then 中的回调保存起来,当执行成功或者失败,状态改变时再执行

 

 

2.resolveFn()和rejected()这里为什么要用setTimeout将它变为异步执行呢?因为如果不用setTimeou这种方式的话,若Promise里面的代码是同步代码,在执行到reject或者resolve的时候,还没有执行then,所以数组里还没有值,这个时候调用的话不会报错但是不会输出任何结果,用setTimeout转为异步的话,会先去执行then方法,将回调收集到数组里,然后再去执行异步任务,这个时候就有值了。举例子:

 此时红色方框内的是同步代码,会先执行,不会输出任何东西,当然如果把红色方框内的变为异步代码就不会有这个问题了。但是我们要同时兼顾同步和异步都存在的情况。

3.then方法:

then(onFullfiled,onRejected){
        if(this.status === 'pending') {
            this.onFullfilledCallback .push(onFullfiled)
            this.onRejectedCallback.push(onRejected)
        }
    }

then很简单,就是在状态为pending的时候将回调函数收集到数组里面,到此异步功能就差不多了,我们写个例子试一下。

let p1 = new myPromise((resolve,reject) =>{
    setTimeout(function(){
        resolve('第一次成功')
    },1000)
})
p1.then(result =>{
    console.log('result:'+result)
},reason =>{
    console.log('reason:'+reason) 
})

 完美。

但是这个第二版还不能实现链式调用,在工作中我们经常通过promis.then().then()这样的方式来解决回调地狱,如下图。

 接下来我们就实现链式调用

第三版:实现链式调用

首先分析,Promise为什么可以实现链式调用,因为Promise.then()方法它返回的是一个新的Promise实例,将这个新的Promise实例的返回值传递到下一个then中,作为下次onFullfilled()或者onRejected()的值。那这个新的Promise的返回值会有哪几种情况呢?我们来分析一下

 

1.如果返回的是一个普通值就直接执行成功,resolve(x)
2.如果返回的是一个promise实例,就继续new
3.如果出错了,就执行失败reject(x)
4.如果参数是null,就会输出undefined
所以要对返回值进行解析。
改写then方法。
then(onFulfiled,onRejected){
        // 声明返回的promise2     
        let promise2 = new myPromise((resolveFn, rejectFn)=>{       
            if (this.status === 'fulfilled') {         
                let x = onFulfiled(this.value);         
                // resolvePromise函数,处理自己return的promise和默认的promise2的关系         
                resolvePromise(promise2, x, resolveFn, rejectFn);       
            };       
            if (this.status === 'rejected') {         
                let x = onRejected(this.reason);         
                resolvePromise(promise2, x, resolveFn, rejectFn);       
            };       
            if (this.status === 'pending') {         
                this.onFullfilledCallback.push(()=>{           
                    let x = onFulfiled(this.value);           
                    resolvePromise(promise2, x, resolveFn, rejectFn);         
                })         
                this.onRejectedCallback.push(()=>{           
                    let x = onRejected(this.reason);           
                    resolvePromise(promise2, x, resolveFn, rejectFn);         
                })       
            }
        });     
        // 返回promise,完成链式     
        return promise2;   
    }

我们首先定义一个新的Promise实例,在这个实例内部需要判断状态,当状态不是等待态时,就去执行相对应的函数。如果状态是等待态的话,就往回调函数中 push 函数。将then回调函数的返回值记为x,由于这个返回值可能有多种情况,所以需要对各种情况进行解析。所以另外封装一个方法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 {       
                        // PromiseA+规定,声明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
                            resolve(x); // 直接成功即可       
                        }     
                    } catch (e) {       
                        // 如果在执行的过程中报错了,就被then的失败捕获       
                        if (called) return;       
                        called = true;       // 取then出错了那就不要在继续执行了       
                        reject(e);      
                    }   
                } else { //如果是普通值     
                    resolve(x);   
                } 
            }
1.PromiseA+规定 x 不能与 promise2 相等,这样会发生循环引用的问题
2.定义一个called来控制调用次数,成功和失败只能调用一个,一旦调用完就将called = true,防止下一个再调用。
3.接着判断x的类型,如果不是对象或者函数,那就是普通值,直接resolve(x)
4.如果是对象或者函数,将x.then赋值给then,如果then是函数,就让then执行回调函数。如果下一次的执行结果还是一个Promise,就接着处理。如果then是个普通对象,就直接执行
resolve方法
5.再执行这段代码的过程中可能会发生异常,我们用try catch去捕获错误,如有错误,就直接执行reject方法。
来测试一下是否可以实现链式调用了:

 没毛病。

到此,Promise的核心功能都已经完成了,Promise还有一些其他的方法,all、race、resolve、reject,相信理解了上面的封装流程,大概就知道怎么封装了,当然前提是要知道这些方法的用法。接下来就看一下这些方法的实现。

Promise其他方法的实现

简单说一下,all方法接收一个数组,等到数组里面的所有Promise实例的状态都变成成功态之后才成功,但只要有一个失败了就返回失败。race方法是哪个先成功就先返回哪个,resolve和reject是分别执行成功和是失败。代码如下

 

//all 获取所有的promise,都执行then,把结果放到数组,一起返回
    static all(promiseArr =[]) {
        return new Promise((resolve,reject) =>{
            let index = 0;
            let arr = []
            for(let i =0; i<promiseArr.length; i++){
                promiseArr[i].then(result =>{
                    index++
                    arr[i] = result
                    if(index === arr.length){
                        resolve(arr)
                    }
                },reason =>{
                    reject(reason)
                })
            }
        })
    }
    //谁先执行先返回谁
    static race (promises){   
        return new Promise((resolve,reject)=>{     
            for(let i=0;i<promises.length;i++){       
                promises[i].then(resolve,reject)     
            };   
        }) 
    }
    //resolve方法 
    static resolve(result){   
        return new Promise((resolve,reject)=>{     
            resolve(result)   
        }); 
    } 
    //reject方法 
    static reject (reason){   
        return new Promise((resolve,reject)=>{     
            reject(reason)   
        }); 
    } 

 

测试:

          

 

 Promise源码就全部已经完成。阅读源码对我们思维能力会有很大的提升的,希望大家不要都可以动手实现一下,相信会有不少收获。

 

 

 

 


































 

Guess you like

Origin www.cnblogs.com/lyt0207/p/12387564.html