Promise原理分析

877 阅读4分钟

Promise成为大家解决异步回调地狱问题的重要方法,理解它的原理和底层实现有助于我们更加合理的使用。以下遵循PromiseA+规范实现了一个简单的Promise.

实现分为六步:

  • 实现同步promise
  • 实现异步promise和多次then
  • 实现promise的链式调用
  • 特殊情况的处理
  • 实现then穿透
  • 测试

实现同步的promise

  • 明确promise有三个状态: pending fulfilled rejected

  • 在new Promise时, excutor会立即执行

  • 每个promise上会有一个then方法, then方法中传递两个参数onFulfilled和onRejected

代码如下:


class Promise{
    constructor(excutor){
        this.status = 'pending'; // 默认是等待态
        this.value = undefined;
        this.reason = undefined;
        let resolve = (value) => { // 调用resolve, 变成成功态
            if (this.status === 'pending') {
                this.status= 'fulfilled';
                this.value = value;
            }  
        };
        let rejected = (reason) => { // 调用reject, 变成失败态
            if (this.status === 'pending') {
                this.status= 'rejected';
                this.reason = reason;
            }         
        };
        excutor(resolve, rejected);
    }
    then(onFulfilled, onRejected){
        console.log(this.status, 'status');
        if (this.status === 'fulfilled') { // 变成成功态fulfilled时执行成功回调
            onFulfilled(this.value);
        }
        if (this.status === 'rejected') { //  变成失败态rejected时执行失败回调
            onRejected(this.reason);
        }
    }
}

实现异步promise和多个then

核心思路: 将成功和失败的回调放在数组中, 当状态改变时, 执行对应状态的事件

class Promise{
    constructor(excutor){
        this.status = 'pending';
        this.value = undefined;
        this.reason = undefined;
        this.onFulfilledCbs = []; // 存放成功状态的回调
        this.onRejectedCbs = []; // 存放失败状态的回调
        let resolve = (value) => {
            if (this.status === 'pending') {
                this.status= 'fulfilled';
                this.value = value;
                this.onFulfilledCbs.forEach(cb => cb()); // 依次执行成功回调
            }  
        };
        let rejected = (reason) => {
            if (this.status === 'pending') {
                this.status= 'rejected';
                this.reason = reason;
                this.onRejectedCbs.forEach(cb => cb());   // 依次执行失败回调
            }  
        };
        excutor(resolve, rejected)
    }
    then(onFulfilled, onRejected){
        ...
        if (this.status === 'pending') { // 有异步逻辑时,状态为pending 将callback放在数组中
            this.onFulfilledCbs.push(() => {
                onFulfilled(this.value);
            });
            this.onRejectedCbs.push(() => {
                onRejected(this.reason);
            });
        }
    }
}

实现then的链式调用

思路: 返回一个新的promise实例. jquery中的方法也能链式调用, 是通过return this实现的.但是promise不能返回this, 因为, 一个promise的状态如果是fulfilled,就不能变成rejected.

then的特点:

  • 无论成功还是失败 会将成功或失败的值传递给下一级
  • 如果没有接受到返回值 会默认执行下个then的
  • 如果抛出错误,没有捕获,会报错
  • 主动抛出异常 走onRejected 此时可以将onFulfilled赋值为null
  • 主动抛出异常也可以通过catch进行捕获 并且 then()可以穿透
  • catch后可以执行then 返回值会传给下个onFulfilled
  • err捕获的原则: 先找最近的捕获,没有找下一个

class Promise{
    constructor(excutor){
        ...
        try{ // 1. 在执行excutor时有可能抛出错误 直接捕获错误
            excutor(resolve, reject);
        }catch(err) {
            reject(err);
        }
        
    }
    // resolvePromise 判断当前promise返回结果x和promise2的关系
    resolvePromise(promise2, x, resolve, reject) {
        if((typeof x === 'object' && x !== null) || typeof x === 'function'){
            try{ // 取then方法时  有可能报错 
                let then = x.then;
                if (typeof then === 'function') { // 2.2.2 返回一个promise 执行promise
                    then.call(x, (y) => {
                        resolve(y);
                    }, (r) => {
                        reject(r);
                    });
                } else { // 2.2.3 返回一个带有then属性的普通对象 {then: ...}
                    resolve(x);
                }
            }catch(e){
                reject(e);
            }
        } else {// 2.2.1 返回一个常量
            resolve(x);
        }
    }
   
    then(onFulfilled, onRejected){ 
        // 2.实现then的链式调用(jq)
        let promise2 = new Promise((resolve, reject) => { // 2.1 返回一个新的promise
            if (this.status === 'fulfilled') {
                try{ // 如果方法执行报错 直接抛出错误
                    let x = onFulfilled(this.value); // 2.2 处理前一个promise中onFulfilled的不同返回结果和下一个promise的关系: 普通值/promise
                    this.resolvePromise(promise2, x, resolve, reject);
                }catch(e){
                    reject(e);
                }
            }
            if (this.status === 'rejected') {
                try{
                    let x = onRejected(this.reason);
                    this.resolvePromise(promise2, x, resolve, reject);
                }catch(e){
                    reject(e);
                }
            }
            if (this.status === 'pending') {
                this.onFulfilledCbs.push(() => {
                    try{
                        let x = onFulfilled(this.value);
                        this.resolvePromise(promise2, x, resolve, reject);
                    }catch(e){
                        reject(e);
                    }
                });
                this.onRejectedCbs.push(() => {
                    try{
                        let x = onRejected(this.reason);
                        this.resolvePromise(promise2, x, resolve, reject);
                    }catch(e){
                        reject(e);
                    }
                });
            }
        });
        return promise2;
    }
}

特殊情况处理

  • 返回的promise2和当前的promise不能相同: 如果相同会导致promise2的循环调用
  • new Promise(resolve, reject)中resolve传递的值可能也是一个promise, 需要实现递归解析
  • 避免promise即调用成功又调用失败
class Promise{
    ...
    resolvePromise(promise2, x, resolve, reject) {
        if (promise2 === x) { // 4.1 return的值不能为promise2(自己等待自己) 否则会报循环引用错误
            return reject(new Error('循环引用'));
        }
        let called; // 4.4 定义called 避免成功失败只能调用一个
        if((typeof x === 'object' && x !== null) || typeof x === 'function'){
            try{
                let then = x.then;
                if (typeof then === 'function') {
                    then.call(x, (y) => {
                        if (called) return; // 成功和失败只会掉一个
                        called = true;
                        this.resolvePromise(promise2, y, resolve, reject); // 4.3 如果y是一个promise 进行递归解析
                    }, (r) => {
                        if (called) return; // 如果掉过成功
                        called = true;
                        reject(r);
                    });
                } else {
                    if (called) return;
                    called = true;
                    resolve(x);
                }
            }catch(e){
                if (called) return;
                called = true;
                reject(e);
            }
        } else {
            if (called) return;
            called = true;
            resolve(x);
        }
    }
   
    then(onFulfilled, onRejected){
        let promise2 = new Promise((resolve, reject) => {
            if (this.status === 'fulfilled') {
                setTimeout(() => { // 4.2 如果不加setTimeout会导致promise2为undefined-->then方法都是异步的
                    try{
                        let x = onFulfilled(this.value);
                        this.resolvePromise(promise2, x, resolve, reject); // 初始化promise2未完成, 为undefined
                    }catch(e){
                        reject(e);
                    }
                });
            }
            if (this.status === 'rejected') {
                setTimeout(() => {
                    try{
                        let x = onRejected(this.reason);
                        this.resolvePromise(promise2, x, resolve, reject);
                    }catch(e){
                        reject(e);
                    }
                });
            }
            ....
        });
        return promise2;
    }
}

module.exports = Promise;

then穿透

思路: 如果promise执行resolve, 就将resolve中的值通过onFULfilled传递; 如果promise执行reject, 就将reject中的值通过onRejected传递

then(onFulfilled, onRejected){
        // 5 穿透实现
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (data) => {
            return data;
        };
        onRejected = typeof onRejected === 'function' ? onRejected : (err) => {
            throw err;
        };
        ...
    }

测试

在promise A+规范中提出, 通过promises-aplus-tests插件对自己写的promise进行检验, 但是这个插件使用前需要通过promise.defer将promise的属性挂载在defer上, 代码如下:

Promise.deferred = Promise.defer = () => {
    let dfd = {};
    dfd.promise = new Promise((resolve, reject) => {
        dfd.resolve = resolve;
        dfd.reject = reject;
    });
    return dfd;
}

下面就可以通过promises-aplus-tests插件对自己写的promise进行检验了:

  • yarn add promises-aplus-tests
  • npx promises-aplus-tests promise.js