JS/ES6 Promise 的不完全实现

零、原文与资料

  1. 手写 Promise

  2. 最简实现Promise,支持异步链式调用(20行)

一、Promise/A+ 规范

1.Promise存在三个状态:pending(等待态)、fulfilled(成功态)、rejected(失败态);
2.pending为初始态,并可以转化为fulfilled和rejected;
3.成功时,不可转为其他状态,且必须有一个不可改变的值 (value);
4.失败时,不可转为其他状态,且必须有一个不可改变的原因 (reason);
5.new Promise(executor = (resolve, reject) => {resolve(value)}), resolve(value)将状态置为 fulfilled;
6.new Promise(executor = (resolve, reject) => {reject(reason)}), reject(reason)将状态置为 rejected;
7.如果 executor 执行异常也会 reject();
8.thenable: then(onFulfilled, onRejected?);
  8.1 onFulfilled: status 为 fulfilled,执行 onFulfilled, 传入 value
  8.2 onRejected: status 为 rejected, 执行 onRejected

二、同步 Promise

同步 Promise 没啥需要特别注意的地方,代码如下:

// 1.Promise存在三个状态:pending(等待态)、fulfilled(成功态)、rejected(失败态)
const STATUS_PENDING = 'pending'
const STATUS_FULFILLED = 'fulfilled'
const STATUS_REJECTED = 'rejected'
class myPromise {
    constructor(executor) {
        // pending为初始态,并可以转化为fulfilled和rejected
        this.status = STATUS_PENDING
        this.value = '' // 3
        this.reason = '' // 4

        let resolve = value => {
            // 5.
            if (this.status === STATUS_PENDING) {
                this.status = STATUS_FULFILLED
                this.value = value
            }
        }
        let reject = reason => {
            //6.
            if (this.status === STATUS_PENDING) {
                this.status = STATUS_REJECTED
                this.reason = reason
            }
        }
        // 7.
        try {
            executor(resolve, reject);
        } catch (err) {
            reject(err);
        }
    }
  // 8.
    then(onFulfilled = () => {}, onRejected = () => {}) {
        // 8.1
        if (this.status === STATUS_FULFILLED) {
            onFulfilled(this.value)
        }
        // 8.2
        if (this.status === STATUS_REJECTED) {
            onRejected(this.reason)
        }
    }
}


let ps = new myPromise(resolve => {
    console.log('before resolve')
    resolve(1)
})
ps.then(res => {
    console.log(res)
})

let pe = new myPromise((resolve, reject) => {
    console.log('before reject')
    reject('reject error')
})
pe.then(res => {
    console.log(res)
}, error => {
    console.log(error)
})

在上面的两个例子中,所有任务的执行都是同步的,可以与接下来的异步 promise 的执行顺序对比下。

三、异步 Promise

先放下我们的运行实例:

let pa = new myPromise(resolve => {
    console.log('before resolve')
    setTimeout(()=>{
        resolve(1)
    },1000)
})
pa.then(res => {
    console.log(res)
})

 在这里,我们的 executor 函数体中有异步的代码块,那么在执行 executor(resolve, reject); 的时候,setTimeout中的任务就会被推入异步执行栈中,等待主线程中的宏任务(详见 EventLoop in Js)全部计算完成再执行这个任务,因此,我们打断点会发现,  then() 的执行会早于  resolve(1) , 而在 then() 执行的时候,pa.status 依然是 pending,接下啦根据逻辑判断 then 函数执行完成退出,然后执行异步任务,整个代码执行完毕,故控制台只会打印出 'before resolve'。
明白了这里的执行顺序,我们即可以进行完善,代码如下:(标记 改 A)

const STATUE_PENDING = 'pending';
const STATUE_FULFILLED = 'fulfilled';
const STATUS_REJECTED = 'rejected';

class MyPromise {
    constructor(executor) {
        // pending 是初始态,并可以转化成 fulfilled 和 rejected
        this.status = STATUE_PENDING;
        this.value = ''; // 3.
        this.reason = ''; // 4.

        // 改 A start
        // 存放成功的数组
        this.onResolvedCallbacks = [];
        // 存放失败的数组
        this.onRejectedCallbacks = [];
        // 改 A end

        let resolve = value => {
            // 5.
            if (this.status === STATUE_PENDING) {
                this.status = STATUE_FULFILLED;
                this.value = value;

                // 改 A start
                // 成功之后的执行栈, 
                this.onResolvedCallbacks.forEach(fn => fn());
                // 改 A end
            }
        }
        
        let reject = reason =>  {
            // 6.
            if (this.status === STATUE_PENDING) {
                this.status = STATUS_REJECTED;
                this.reason = reason;

                // 改 A start
                // 成功之后的执行栈, 
                this.onRejectedCallbacks.forEach(fn => fn());
                // 改 A end
            }
        }


        // 7. 
        try {
            executor(resolve, reject);
        } catch (error) {
            reject(err);
        }
    }

    // 8
    then(onFulfilled = () => {}, onRejected = () => {}){
        // 8.1
        if (this.status === STATUE_FULFILLED) onFulfilled(this.value);
        // 8.2
        if (this.status === STATUS_REJECTED) onRejected(this.reason);

        // 改 A start
        // 如果是处于异步任务的
        if (this.status === STATUE_PENDING) {
            // 推入相应的执行栈
            this.onResolvedCallbacks.push(() => onFulfilled(this.value));
            this.onRejectedCallbacks.push(() => onRejected(this.reason));
        }
        // 改 A end
    }
}

为什么要分析下这边的执行顺序,一来复习下 EventLoop,二来对下面的链式调用的理解比较重要(这里也是打断点才发现的,推翻了一直以来对 Promise.then 的理解,之前一直认为 executor 中的异步任务执行完了才真正的去执行 then 函数和里面的onFulfilled/onRejected 函数)。

四、new Promise().then().then()... 链式调用

Promise 的链式调用和 jquery 的链式调用是不同的,在 jquery 或者一些其他的三方包中,我们在函数末尾加上 return this  即可实现,所以这里无论链多少,都是在同一个对象上做文章。而 Promise 的每一次(链式)调用,其都会产生一个新的 Promise 对象, 并基于这个新的 Promise 调用 then 函数,虽然我们写的时候是.then().then()...。 首先我们来看实例:

let pc = new MyPromise((resolve, reject) => {
    console.log(0);
    setTimeout(() => {
        resolve(1);
    }, 3000);
})
pc.then(res => {
    console.log(res);

    return new MyPromise(resolve => {
        console.log(2);
        setTimeout(() => {
            resolve(3)
        }, 3000);
    })
}).then(res => {
    console.log(res);
})

注意下结构,我们在第一个 then 的参数函数中会有一个新的 Promise 返回。
然后是 MyPromise 类:

const STATUE_PENDING = 'pending';
const STATUE_FULFILLED = 'fulfilled';
const STATUS_REJECTED = 'rejected';

function resolvePromise(promise2, x, resolve, reject) {
    // 处理循环引用报错
    if (x === promise2) {
        // reject 报错
        return reject(new TypeError('chaining cycle detected for promise'));
    }

    // 记录, 防止多次调用
    let called;

    // x 是对象(不包括 null)或者函数
    if (x != null && (typeof x === 'object' || typeof x === 'function')) {
        try {
            // A+ 规定,声明 then = x 的 then 方法
            let then = x.then;
            // then 是 function,则默认是 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 (error) {
            // 出错,即失败
            if (called) return;
            called = true;

            // 取 then 出错了那就不继续了
            reject(error);
        }
    } else {
        resolve(x);
    }
}

class MyPromise {
    constructor(executor) {
        this.status = STATUE_PENDING;
        this.value = '';
        this.reason = '';


        this.onResolvedCallbacks = [];
        this.onRejectedCallbacks = [];

        let resolve = value => {
            if (this.status === STATUE_PENDING) {
                this.status = STATUE_FULFILLED;
                this.value = value;

                this.onResolvedCallbacks.forEach(fn => fn());
            }
        }
        
        let reject = reason =>  {
            if (this.status === STATUE_PENDING) {
                this.status = STATUS_REJECTED;
                this.reason = reason;

                this.onRejectedCallbacks.forEach(fn => fn());
            }
        }

        try {
            executor(resolve, reject);
        } catch (error) {
            reject(err);
        }
    }

    then(onFulfilled, onRejected){
        // onFulfilled 不是函数, 则忽略,直接返回 value
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
        // onRejected 不是函数, 则忽略,直接扔出错误
        onRejected = typeof onRejected === 'function' ? onRejected : err => {
            throw err;
        };

        let promise2 = new MyPromise((resolve, reject) => {
            if (this.status === STATUE_FULFILLED) {
                // 推一个异步任务
                setTimeout(() => {
                    try {
                        let x = onFulfilled(this.value);
                        resolvePromise(promise2, x, resolve, reject);    
                    } catch (error) {
                        reject(error)
                    }
                }, 0);
            }

            if (this.status === STATUS_REJECTED) {
                // 推一个异步任务
                setTimeout(() => {
                    try {
                        let x = onRejected(this.reason);
                        resolvePromise(promise2, x, resolve, reject);    
                    } catch (error) {
                        reject(error)
                    }
                }, 0);
            }

            if (this.status === STATUE_PENDING) {
                // 推到执行栈中
                this.onResolvedCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            let x = onFulfilled(this.value);
                            resolvePromise(promise2, x, resolve, reject);    
                        } catch (error) {
                            reject(error)
                        }
                    }, 0);
                });

                this.onRejectedCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            let x = onRejected(this.reason);
                            resolvePromise(promise2, x, resolve, reject);    
                        } catch (error) {
                            reject(error)
                        }
                    }, 0); 
                })
            }

        });

        return promise2;
    }
}

这里要注意的是 then 函数的执行会产生一个新的 Promise, 第一个 then 函数的参数函数的执行也会产生一个新的 Promise。

五、其他:catch、resolve、reject、race和all

这里除 catch 外其余均是静态方法:

 1. catch(特殊的 then 方法):

catch(fn){
    return this.then(null,fn)
}

 2.reslove(resolve 一个值)

MyPromise.resolve = val => new Promise(resolve=> resolve(val))

 3.reject(reject 一个值)

MyPromise.reject = val => new Promise((resolve,reject)=> reject(val))

 4.race Promise.race([p1, p2, p3])里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。

MyPromise.race = promises => {
    return new MyPromise((resolve, reject) =>
        promises.forEach(pro => pro.then(resolve, reject))
    )
}

 5.all Promise.all可以将多个Promise实例包装成一个新的Promise实例。同时,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值。

MyPromise.all = function (promises) {
    return new Promise((resolve, reject) => {
        let index = 0;
        let result = [];
        if (promises.length === 0) {
            resolve(result);
        } else {
            function processValue(i, data) {
                result[i] = data;
                if (++index === promises.length) {
                    resolve(result);
                }
            }
            for (let i = 0; i < promises.length; i++) {
                //promises[i] 可能是普通值
                Promise.resolve(promises[i]).then((data) => {
                    processValue(i, data);
                }, (err) => {
                    reject(err);
                    return;
                });
            }
        }
    });
}

六、一点感悟

Promise/A+ 规范还是值得每一个合格的前端开发去阅读的。

    得:再一次复习了 eventLoop,推翻了之前对 then 函数的执行时机的错误认识;

    目标:完全理清楚链式调用的任务栈和执行顺序以及 Promise.all 的原理;

原文地址:https://www.cnblogs.com/cc-freiheit/p/12659992.html