异步之优雅的解决方案——promise(下)

114 阅读6分钟

0. 前言

前文之路:异步之优雅的解决方案——promise(上)

4. 手写 Promise

4.1 Promise骨架实现

在前面的内容中,我们知道Promise构造器的用法为:

var p = new Promise( function(resolve, reject){
    // resolve()用于决议/完成这个promise 
    // reject()用于拒绝这个promise
})

除此之外,Promise对象一定会有一个.then()方法。从这些信息中,我们可以得到一个基本的Promise对象,应该有这几个要素:

function Promise(excutor){

}

Promise.prototype.then = function(onfulfilled, onrejected) {

}

我们需要往这个骨架里面添加代码。我们知道excutor是一个函数,它包含两个函数类型的参数resolvereject。我们还知道Promise有三种状态:pending、fulfilled、rejected,Promise还应该有处理的数据和拒绝的原因。添加了这些信息的初步代码如下:

function Promise(excutor) {
    this.state = 'pending';
    this.value = null;
    this.reason = null;
    
    const resolve = value => {
        this.value = value;
    };
    const reject = reason => {
        this.reason = reason;
    };
    
    excutor(resolve, reject);
}
Promise.prototype.then = function(onfulfilled = Function.prototype, onrejected = Function.prototype) {
    onfulfilled(this.value);
    
    onreject(this.reason);
}

这一版代码只是初步的将Promise对象应该具有的信息添加进去,但是并没有实现Promise状态改变和异步处理的逻辑。

首先来看Promise状态的改变。我们知道Promise的状态只能从pendding变成fulfilled或者rejected,并且这个过程是不可逆的。而且,onfulfilled函数只能在状态变成fulfilled时才会执行,并且只执行一次;onreject函数只能在状态变成rejected时才会执行,并且只执行一次。我们需要加一个简单的判断。代码如下:

function Promise(excutor) {
    this.state = 'pending';
    this.value = null;
    this.reason = null;
    
    const resolve = value => {
        if(this.state === 'pending'){
            this.value = value;
            this.state = 'fulfilled';
        }
    };
    const reject = reason => {
       if(this.state === 'pending'){
            this.value = reason;
            this.state = 'rejected';
        }
    };
    
    excutor(resolve, reject);
}
Promise.prototype.then = function(onfulfilled, onrejected) {
    onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data;
    onrejected = typeof onrejected === 'function' ? onrejected : error => {throw error};
    
    if(this.state === 'fulfilled') {
        onfulfilled(this.value);
    }
    
    if(this.state === 'rejected') {
        onreject(this.reason);
    } 
}

在解决了Promise状态改变的问题之后,我们开始着手解决Promise异步编程的问题。异步编程要解决的问题就是.then()方法中定义的函数不能直接调用,要满足某种条件之后才可以调用。那怎么办呢?我们应该在合适的时间去调用onfulfilled方法,这个合适的时间应该是开发者调用resolve()的时刻。这段代码这样来写:

function Promise(excutor) {
    this.state = 'pending';
    this.value = null;
    this.reason = null;
    this.onFulfilledFunc = Function.prototype;
    this.onRejectedFunc = Function.prototype;
    
    const resolve = value => {
        if(this.state === 'pending'){
            this.value = value;
            this.state = 'fulfilled';
            
            this.onFunfilledFunc(this.value);
        }
    };
    const reject = reason => {
       if(this.state === 'pending'){
            this.value = reason;
            this.state = 'rejected';
            
            this.onRejectedFunc(this.reason);
        }
    };
    
    excutor(resolve, reject);
}
Promise.prototype.then = function(onfulfilled, onrejected) {
    onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data;
    onrejected = typeof onrejected === 'function' ? onrejected : error => {throw error};
    
    if(this.state === 'fulfilled') {
        onfulfilled(this.value);
    }
    
    if(this.state === 'rejected') {
        onreject(this.reason);
    } 
    
    if(this.state === 'pending') {
        this.onFulfilledFunc = onfulfilled;
        this.onRejectedFunc = onrejected;
    }
}

这样,我们的代码就可以异步执行了。但是,这一版的代码还是存在一些小的缺陷。举个栗子

let p = new Promise((resolve, reject) => {
    resolve('data');
});

p.then(data => {
    console.log(data);
});

console.log(1);

在正常的Promise中,这段代码的输出应该是先输出1,然后再输出data;而在我们自己的Promise中,则是先输出data,后输出1。这跟两行代码放入事件循环队列的顺序有关。在我们的代码中,先定义了.then()方法,因此,它先被放入事件循环队列。而在实际的Promise中,将所有同步的代码都变成了异步的,所以.then()方法应该是要放到所有同步执行的代码之后的。我们这样改进:

function Promise(excutor) {
    this.state = 'pending';
    this.value = null;
    this.reason = null;
    this.onFulfilledFunc = Function.prototype;
    this.onRejectedFunc = Function.prototype;
    
    const resolve = value => {
        if(value instanceof Promise) {
            return value.then(resolve, reject);
        }
        setTimeout(() => {
            if(this.state === 'pending'){
                this.value = value;
                this.state = 'fulfilled';

                this.onFunfilledFunc(this.value);
            }
        });
        
    };
    const reject = reason => {
        setTimeout(() => {
            if(this.state === 'pending'){
                this.value = value;
                this.state = 'rejected';

                this.onRejectedFunc(this.reason);
            }
        });
    };
    
    excutor(resolve, reject);
}
Promise.prototype.then = function(onfulfilled, onrejected) {
    onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data;
    onrejected = typeof onrejected === 'function' ? onrejected : error => {throw error};
    
    if(this.state === 'fulfilled') {
        onfulfilled(this.value);
    }
    
    if(this.state === 'rejected') {
        onreject(this.reason);
    } 
    
    if(this.state === 'pending') {
        this.onFulfilledFunc = onfulfilled;
        this.onRejectedFunc = onrejected;
    }
}

这样就可以正常实现了。除此之外,正常的Promise中还可以添加多个.then()方法,然后依次执行多个.then()方法。而我们的Promise则会覆盖掉旧的方法。因此,我们采用数组将多个方法存起来,然后依次执行。代码如下:

function Promise(excutor) {
    this.state = 'pending';
    this.value = null;
    this.reason = null;
    this.onFulfilledArray = [];
    this.onRejectedArray = [];
    
    const resolve = value => {
        if(value instanceof Promise) {
            return value.then(resolve, reject);
        }
        setTimeout(() => {
            if(this.state === 'pending'){
                this.value = value;
                this.state = 'fulfilled';

                this.onFunfilledArray.forEach(func => {
                    func(value)
                });
            }
        });
        
    };
    const reject = reason => {
        setTimeout(() => {
            if(this.state === 'pending'){
                this.value = value;
                this.state = 'rejected';

                this.onRejectedArray.forEach(func => {
                    func(reason)
                });
            }
        });
    };
    
    excutor(resolve, reject);
}
Promise.prototype.then = function(onfulfilled, onrejected) {
    onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data;
    onrejected = typeof onrejected === 'function' ? onrejected : error => {throw error};
    
    if(this.state === 'fulfilled') {
        onfulfilled(this.value);
    }
    
    if(this.state === 'rejected') {
        onreject(this.reason);
    } 
    
    if(this.state === 'pending') {
        this.onFulfilledFunc = onfulfilled;
        this.onRejectedFunc = onrejected;
    }
}

最后一个需要完善的细节是,在构造函数中如果出错,将会自动触发Promise实例状态变为rejected,在这里,我们用try...catch...模块包裹excutor。

try {
    excutor(resolve, reject)
} catch(e) {
    reject(e)
}

到此,我们的Promise骨架就基本实现了。现在,我们来总结一下Promise骨架实现的几个要点:

  • Promise的状态具有凝固性
  • Promise可以在then方法第二个参数中进行错误处理
  • Promise实例可以添加多个then处理场景

4.2 Promise 链式调用

链式调用的本质就是返回一个Promise对象。因此,我们的代码这样修改.

  1. 首先,我们在then方法中创建一个新的Promise实例并返回。代码如下:
Promise.prototype.then = function(onfulfilled, onrejected) {
    onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data;
    onrejected = typeof onrejected === 'function' ? onrejected : error => {throw error};    
    
    // promise2将作为then方法的返回值
    
    let promise2;
    if(this.state === 'fulfilled') {
        return promise2 = new Promise((resolve, reject) => {
            setTimeout(() => {
                try {
                    let result = onfulfilled(this.value);
                    resolve(result);
                } catch(e) {
                    reject(e);
                } 
            });
        });
    }
    
    if(this.state === 'rejected'){
        return promise2 = new Promise((resolve, reject) => {
            setTimeout(() => {
                try {
                    let result = onrejected(this.reason);
                    resolve(result);
                } catch(e) {
                    reject(e);
                } 
            });
        });
    }
    
    if(this.state === 'pending') {
        return promise2 = new Promise(resolve, reject) => {
            this.onFulfilledArray.push(() => {
                try {
                    let result = onfulfilled(this.value);
                    resolve(result);
                } catch(e) {
                    reject(e);
                }
            });
            
            this.onRejectedArray.push(() => {
                try {
                    let result = onrejected(this.reason);
                    resolve(result);
                } catch(e) {
                    reject(e);
                }
            });
        });
    }
}

有的时候,Promise的链式调用会变得很复杂。在这里,我们抽象一个函数resolvePromise()来处理Promise.这个函数需要传入四个参数:

  • promise2:返回的Promise实例
  • result:onfulfilled或onrejected函数的返回值
  • resolve:promise2的resolve方法
  • reject:promise2的reject方法

代码如下:

const resolvePromise = (promise2, result, resolve, reject) => {
    
};
Promise.prototype.then = function(onfulfilled, onrejected) {
    onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data;
    onrejected = typeof onrejected === 'function' ? onrejected : error => {throw error};    
    
    // promise2将作为then方法的返回值
    
    let promise2;
    if(this.state === 'fulfilled') {
        return promise2 = new Promise((resolve, reject) => {
            setTimeout(() => {
                try {
                    let result = onfulfilled(this.value);
                    resolvePromise(promise2, result, resolve,reject);
                } catch(e) {
                    reject(e);
                } 
            });
        });
    }
    
    if(this.state === 'rejected'){
        return promise2 = new Promise((resolve, reject) => {
            setTimeout(() => {
                try {
                    let result = onrejected(this.reason);
                    resolvePromise(promise2, result, resolve,reject);
                } catch(e) {
                    reject(e);
                } 
            });
        });
    }
    
    if(this.state === 'pending') {
        return promise2 = new Promise(resolve, reject) => {
            this.onFulfilledArray.push(() => {
                try {
                    let result = onfulfilled(this.value);
                    resolvePromise(promise2, result, resolve,reject);
                } catch(e) {
                    reject(e);
                }
            });
            
            this.onRejectedArray.push(() => {
                try {
                    let result = onrejected(this.reason);
                    resolvePromise(promise2, result, resolve,reject);
                } catch(e) {
                    reject(e);
                }
            });
        });
    }
};

然后,我们来实现这个函数。废话不多说,上代码:

const resolvePromise = (promise2, result, resolve, reject) => {
    // 当result === promise2,也就是在onfulfilled返回promise2时,执行reject
    if(result === promise2) {
        reject(new TypeError('error due to circular reference'));
    }
    
    // 是否已经执行过onfulfilled或onrejected
    let consumed = false;
    let thenable;
    
    if(result instanceof Promise) {
        if(result.state === 'pending'){
            result.then(function(data) {
                resolvePromise(promise2, data, resolve, reject)
            });
        } else {
            result.then(resolve, reject);
        }
        return;
    }
    
    let isComplexResult = target => (typeof target === 'function' || typeof target === 'object') && (target !== null);
    
    if(isComplexResult(result)){
        try {
            thenable = result.then;
            // 判断返回值是否为 Promise 类型
            if(typeof thenable === 'function') {
                thenable.call(result, function(data) {
                    if(consumed){
                        return;
                    }
                    consumed = true;
                    
                    return resolvePromise(promise2, data, resolve, reject);
                }, function(error) {
                    if(consumed) {
                        return;
                    }
                    cosumed = true;
                    
                    return reject(error);
                });
            } else {
                resolve(result);
            }
        } catch(e) {
            if(consumed) {
                return;
            }
            consume = true;
            return reject(e);
        }
    } else {
        resolve(result);
    }
};

总结一下,这段代码主要集中对于Promise决议之后返回一个Promise对象的问题进行了解决,实现了Promise的链式调用。解决途径是,如果返回的值是Promise类型的,则持续调用resolvePromise函数进行决议,直到遇到非Promise类型的值。遇到一般值,则直接调用resolve方法进行决议。

4.3 Promise 其他方法实现

  1. Promise.prototype.catch()
Promise.prototype.catch = function(catchFunc) {
    return this.then(null, catchFunc);
};
  1. Promise.resolve()
Promise.resolve = function(value) {
    return new Promise((resolve, reject) => {
        resolve(value);
    });
};
  1. Promise.reject()
Promise.reject = function(value) {
    return new Promise((resolve, reject) => {
        reject(value);
    });
};
  1. Promise.all()
Promise.all = function(promiseArray) {
    if(!Array.isArray(promiseArray)){
        throw new TypeError('The arguments should be an array');
    }
    
    return new Promise((resolve, reject) => {
        try {
            let resultArray = []
            
            const length = promiseArray.length
            
            for(let i = 0; i < length; i++) {
                promiseArrayp[i].then(data => {
                    resultArray.push(data)
                    
                    if(resultArray.length === length) {
                        resolve(resultArray);
                    }
                }, reject);
            }
        } catch(e) {
            reject(e);
        }
    });
};
  1. Promise.race()
Promise.race = function(value) {
    if(!Array.isArray(promiseArray)){
        throw new TypeError('The arguments should be an array');
    }
    
    return new Promise((resolve, reject) => {
        try {         
            const length = promiseArray.length
            
            for(let i = 0; i < length; i++) {
                promiseArrayp[i].then(resolve, reject);
            }
        } catch(e) {
            reject(e);
        }
    });
};