剖析Promise核心概念和原理

257 阅读8分钟

异步的概念

  • 同步、异步指代的是被调用方状态,阻塞、非阻塞指的是调用方的状态

  • Javascript语言的执行环境是"单线程",所谓"单线程",就是指一次只能完成一件任务。如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务,以此类推。

  • 这种模式的好处是实现起来比较简单;坏处是只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。

  • 为了解决这个问题,Javascript语言将任务的执行模式分成两种:同步(Synchronous)和异步(Asynchronous)。

  • js解决异步的问题本质上都是回调,在适当的时机触发回调函数。

  • 什么时候使用异步(耗时操作,并不影响同步代码逻辑)

  1. 事件监听函数的回调
  2. 定时器函数的回调
  3. 网络请求
  4. io操作

异步代码

  1. 回调函数:比如在node中,读一个文件
    fs.readFile('文件路径.js', (err, res)=>{
        // 当读取完文件后,会触发这个回调函数,将读取结果作为参数传递过来
    })

回调函数的优点是简单、容易理解,缺点是不利于代码的阅读和维护,各个部分之间高度耦合,流程会很混乱,多层的嵌套会造成“回调地狱”。

  1. 事件监听
  • 采用事件驱动模式。任务的执行不取决于代码的顺序,而取决于某个事件是否发生。
    dom.onclick = () => {
        // 点击后,将事件回调放到宏任务队列
    })
  1. 发布/订阅
  • 我们假定,存在一个"信号中心",某个任务执行完成,就向信号中心"发布"(publish)一个信号,其他任务可以向信号中心"订阅"(subscribe)这个信号,从而知道什么时候自己可以开始执行。这就叫做"发布/订阅模式"(publish-subscribe pattern)。
  • 流程应该是先订阅,再发布
  // jQuery 
 jQuery.subscribe("done", f2);
 function f1(){
    setTimeout(function () {
      // f1的任务代码
      jQuery.publish("done");
    }, 1000);
  }

这种方法的性质与"事件监听"类似,但是明显优于后者。因为我们可以通过查看"消息中心",了解存在多少信号、每个信号有多少订阅者,从而监控程序的运行。

promise

promise将回调代码打平,解决了回调地狱的问题,es6按照promiseA+规范实现了官方的promise。

    const p1 = new Promise((resolve, reject)=>{
        resolve(1);
    })

promise的状态

  • 本质上,Promise也是一个回调。new一个Promise实例,将回调函数executor传入Promise,在Promise中,会将resolvereject函数作为executor函数的参数传递过来,当调用resolve或者reject函数,会触发Promse的状态PromiseState和值PromiseResult的改变。
  • 在调用resolve或者rejectPromsie状态会变成已决状态,在已决状态时,再次调用resolvereject是没有效果的,也就是决定状态的函数只能调用一次。
  • 代码模拟实现
    // resolve函数执行后,promise的状态(已决状态)
    const FULFILLED = 'fulfilled';
    // reject函数执行后,promise的状态(已决状态)
    const REJECTED = 'rejected';
    // 未调用resolve、reject函数的状态(未决状态)
    const PENDING = 'pending';
    class Promise{
          constructor(executor) {
            // promise的状态
            this.PromiseState = PENDING;
            
            // promise的值
            this.PromiseResult = undefined;
            const run = (state, result) => {
                // 如果不是pending状态,不能修改状态
                if (this.PromiseState !== PENDING) return;
                this.PromiseState = state;
                this.PromiseResult = result;
            };
            // resolve函数,调用后会将promise状态改为fulfilled
            const resolve = (result) => {
                if (this.PromiseState !== PENDING) return;
                run(FULFILLED, result);
            };
            // reject函数,调用后会将promise状态改为rejected
            const reject = (err) => {
                if (this.PromiseState !== PENDING) return;
                run(REJECTED, err);
            };

            // 直接执行executor函数,将resolve,reject函数作为参数传入,所以executor函数里面的代码是同步的
            try {
                executor(resolve, reject);
            } catch (error) {
                // 如果执行executor过程中发生错误,直接调用reject
                reject(error);
            }
        }
    }

Promise then链的运行过程

  • 上面是Promise改变状态和值的核心代码,而Promise的核心是then链
  • 实例的then函数会有两个回调函数onFulfilledonRejected,当此时的状态为fulfilled的时候,会将onFulfilled函数放入异步任务队列,当此时状态为rejected的时候,会将onRejected函数放入异步任务队列,当此时的状态为pending的时候,会将对应的函数放到对应的待执行队列onFulfilledCallbacks中,等到状态变为已决的时候,再去执行对应待执行队列所有的回调。
  • 在执行onFulfilledonRejected的时候,会将此时的PromiseResult作为参数传入,所以在then的回调函数中,可以拿到之前resolve的结果。
  • then函数会返回一个新的Promise实例,继续进行Promise参数,就形成了then链
  • 注册then的时候,Promise状态为fulfilled(rejected同理)
    const p = new Promise((resolve, reject)=>{
        // 直接改变状态和值
        resolve(1);
    })
    
    // 调用then的时候Promise状态为fulfilled,所以直接将回调放入异步任务队列执行
    const p1 = p.then(res=>{
        console.log(res); // 1
    });
  • 调用then的时候,还没有触发resolve是未决状态,Promise状态为pending
    const p = new Promise((resolve, reject)=>{
        // 一秒钟后将状态改为fulfilled,结果改为1
        setTimeout(()=>{
            resolve(1);
        }, 1000);
    })
    
    // 此时调用then的时候为pending状态,将回调加入待执行队列onFulfilledCallbacks中
    // 当上面resolve过了一秒执行后,才会将下面的回调放入异步队列中执行,并可以拿到resolve的结果
    const p1 = p.then(res=>{
        console.log(res); // 1
        return res * 10;
    });
    p1.then(res=>{
        // 拿到上面then回调的返回值
        console.log(res); // 10
    });
  • 调用then的时候Promise状态为rejected
    const p = new Promise((resolve, reject)=>{
        reject(1);
    })
    
    // promise状态为rejected,将then的第二个回调函数放到异步任务队列执行
    // then函数的两个回调执行过程中只要不报错,执行完都会将promise状态变为fulfilled
    const p1 = p.then(res=>{
        console.log(res); // 第一个回调函数不执行
        return res * 10;
    }, err=>{
        console.log(err); // 1
        return err * 100;
    });
    
    // p1的状态是fulfilled,值是上面onRejected回调的返回值
    p1.then(res=>{
        // 拿到上面then回调的返回值
        console.log(res); // 100
    });
  • then回调返回一个新的Promise np,会在np的状态变为已决状态后,才会改变当前promise的状态,触发then回调执行。
    const p = new Promise((resolve, reject)=>{
        resolve(1);
    })
    
    const p1 = p.then(res=>{
        console.log(res); // 1
        return new Promise((resolve, reject)=>{
            setTimeout(()=>{
                resolve('promise resolve')
            }, 1000);
        });
    });
    
    // 等待返回的promise变为已决状态(一秒后执行回调)
    p1.then(res=>{
        console.log(res); // promise resolve
    });
  • 当then回调没有值的时候,会透传下去。
    const p = new Promise((resolve, reject)=>{
        resolve(1);
    })
 
    // then回调为空
    const p1 = p.then(null, null);
    
    // 拿到透传下来的结果
    p1.then(res=>{
        console.log(res); // 1
    });

Promise then链实现

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

// onFulfilled回调等待队列
+ this.onFulfilledCallbacks = [];

// onRejected回调等待队列
+ this.onRejectedCallbacks = [];

class MyPromise {

    constructor(executor) {
        this.PromiseState = PENDING;
        this.PromiseResult = undefined;
        const run = (state, result) => {
            if (this.PromiseState !== PENDING) return;

            this.PromiseState = state;
            this.PromiseResult = result;
            
            // 调用已决状态后,触发对应的待执行回调执行
            + setTimeout(() => {
            +    if (state === FULFILLED)
            +        this.onFulfilledCallbacks.forEach(callback => callback(this.PromiseResult));
            +    else
            +        this.onRejectedCallbacks.forEach(callback => callback(this.PromiseResult));
            + });
        };
        const resolve = (result) => {
            if (this.PromiseState !== PENDING) return;
            run(FULFILLED, result);
        };
        const reject = (err) => {
            if (this.PromiseState !== PENDING) return;
            run(REJECTED, err);
        };

        try {
            executor(resolve, reject);
        } catch (error) {
            reject(error);
        }
    }
    resolvePromise(x, resolve, reject) {
        // 如果x是个对象,则x可能是promise对象
        if (x && typeof x === 'object') {
            const then = x.then;
            try {
                // x是一个promise实例
                if (typeof then === 'function') {
                    // 手动添加onFulfilled和onRejected函数,后续继续递归调用resolvePromise处理
                    then.call(x, function (y) {
                        resolvePromise(y, resolve, reject);
                    }, function (r) {
                        reject(r);
                    });
                }
            } catch (error) {
                reject(error);
            }
        } 
        // 普通值,直接调用resolve(x),将then返回的promise状态改为已决
        else {
            resolve(x);
        }
    }
    then(onFulfilled, onRejected) {
        // 如果onFulfilled、onRejected为空,赋值默认值透传下去
        if (typeof onFulfilled !== 'function') onFulfilled = value => value;
        if (typeof onRejected !== 'function') onRejected = err => throw err;

        // 返回一个新的promise
        return new MyPromise((resolve, reject) => {
        
            // 如果状态为fulfilled,将onFulfilled放入异步任务队列执行
            if (this.PromiseState === FULFILLED) {
                setTimeout(() => {
                    try {
                        // 拿到onFulfilled的返回值
                        const x = onFulfilled(this.PromiseResult);
                        // x可能是普通值或者是promise实例,交给resolvePromise处理
                        // 会调用新promise的resolve将状态改为已决,并传入上面的返回值
                        this.resolvePromise(x, resolve, reject);
                    } catch (error) {
                        // 执行过程中发生错误,直接reject
                        reject(error);
                    }
                });
            } 
            
            // 如果状态为rejected,将onRejected放入异步任务队列执行
            else if (this.PromiseState === REJECTED) {
                setTimeout(() => {
                    try {
                        const err = onRejected(this.PromiseResult);
                        // 拿到onRejected返回值,继续处理,如果没有发生错误,最终还会调用resolve将状态变为fulfilled
                        this.resolvePromise(err, resolve, reject);
                    } catch (error) {
                        reject(error);
                    }
                });
            } 
            
            // 如果状态为pending,将onFulfilled和onRejected处理逻辑放到对应的等待队列中
            else {
                onFulfilledCallbacks.push(promiseResult => {
                    setTimeout(() => {
                        try {
                            const result = onFulfilled(promiseResult);

                            this.resolvePromise(result, resolve, reject);
                        } catch (error) {
                            reject(error);
                        }
                    });
                });
                onRejectedCallbacks.push(promiseResult => {
                    setTimeout(() => {
                        try {
                            const result = onRejected(promiseResult);

                            this.resolvePromise(result, resolve, reject);
                        } catch (error) {
                            reject(error);
                        }
                    });
                });
            }
        });
    }
    catch(onRejected) {
         return this.then(null, onRejected);
    }
    finally(fn) {
        // 执行回调,穿透到下一个then/catch
        return this.then(data => {
            return Promise.resolve(fn()).then(() => data);
        }, err => {
            return Promise.resolve(fn()).then(() => {
                throw Error(err);
            });
        });
     }
}

Promise其他核心方法

Promise.resolve & Promise.reject

Promise.resolve

    Promise.resolve = function (value) {
        return new Promise(function (resolve) {
            resolve(value);
        });
    };

Promise.reject

  Promise.reject = function (reason) {
        return new Promise(function (_, reject) {
            reject(reason);
        });
    };

Promise.all

Promise.all 所有promise实例都是fulfilled状态,返回的promise才变为fulfilled状态,并将所有实例的解决作为数组传递给返回的promise结果中,其中一个为rejected状态,则返回的promise状态也变成rejected

    Promise.all = function (promiseArr) {
        return new Promise(function (resolve, reject) {
            // 记录当前处理了几个实例
            let index = 0,
                // 结果数组
                ret = [];
            // 循环遍历promise实例数组
            for (let i = 0; i < promiseArr.length; i++) {
                // 取出每一个promise实例
                let proItem = promiseArr[i];
                // 如果元素不是promise对象,直接将对应数组位置赋值为当前值,index记录加一
                if (!(proItem instanceof Promise)) {
                    index++;
                    // 为了保证结果的稳定性,利用i向ret里面添加结果
                    ret[i] = proItem;
                    if (index === promiseArr.length) {
                        // 将最终的结果数组resolve
                        resolve(ret);
                    }
                    return;
                }
                // 当前proItem是promise实例,调用then方法获取resolve/reject处理结果
                proItem.then(function (result) {
                    index++;
                    // 将获取的结果赋值给数组对应位置
                    ret[i] = result;
                    // 如果index记录等于promise实例数组长度,说明所有promise实例已经处理完毕
                    if (index === promiseArr.length) {
                        // 将最终的结果数组resolve
                        resolve(ret);
                    }
                }).catch(function (err) {
                    // 只要有一个失败就整体失败
                    reject(err);
                });
            }
        });
    };

Promise.race

promise参数数组实例有一个变为已决状态,返回的promise则变成和已决实例相同的状态

    Promise.race = function (promiseArr) {
        return new Promise((resolve, reject) => {
            for (let i = 0; i < promiseArr.length; i++) {
                let proItem = promiseArr[i];

                if (!(proItem instanceof Promise)) {
                    resolve(proItem);
                } else {
                    // 是promise实例
                    proItem.then(result => {
                        resolve(result);
                    }, err => {
                        reject(err);
                    });
                }
            }
        });
    };