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 的原理;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章