Promise源碼解析俄删、Generator 和 Async基礎(chǔ)

一疗锐、Promise源碼實現(xiàn)

  • 定義初始類型
// 三種狀態(tài)類型
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
 // 新建兩個數(shù)組, 來分別存儲成功和失敗的回調(diào), 調(diào)用then的時候, 如果還是pending就存入數(shù)組.
FULFILLED_CALLBACK_LIST = [];
REJECTED_CALLBACK_LIST = [];
_status = PENDING;
  • 設(shè)置初始狀態(tài)
    // 入?yún)⑹且粋€函數(shù), 函數(shù)接收resolve和reject兩個參數(shù).
    constructor(fn) {
        // 設(shè)置初始狀態(tài)為pending
        this.status = PENDING;
        this.value = null;
        this.reason = null;
        // 在初始化promise的時候, 就要同步執(zhí)行這個函數(shù), 并且有任何報錯都要通過reject拋出去
        try {
            // 將resolve愉豺、reject的this指向當前執(zhí)行環(huán)境
            fn(this.resolve.bind(this), this.reject.bind(this));
        } catch (e) {
            this.reject(e);
        }
    }
  • 監(jiān)聽狀態(tài)變化
    get status() {
        return this._status;
    }

    // 在status發(fā)生變化的時候, 就執(zhí)行所有的回調(diào)
    set status(newStatus) {
        this._status = newStatus;
        switch (newStatus) {
            case FULFILLED: {
                this.FULFILLED_CALLBACK_LIST.forEach(callback => {
                    callback(this.value);
                });
                break;
            }
            case REJECTED: {
                this.REJECTED_CALLBACK_LIST.forEach(callback => {
                    callback(this.reason);
                });
                break;
            }
        }
    }
  • resolve 和 reject 方法允乐,這兩個方法是要更改status的, 入?yún)⒎謩e是value 和 reason. 從pending改到fulfilled/rejected.
    resolve(value) {
        if (this.status === PENDING) {
            this.value = value;
            this.status = FULFILLED;
        }
    }

    reject(reason) {
        if (this.status === PENDING) {
            this.reason = reason;
            this.status = REJECTED;
        }
    }
  • 定義then方法
    // then接收兩個參數(shù), onFulfilled 和 onRejected
    then(onFulfilled, onRejected) {
        // 檢查并處理參數(shù), 之前提到的如果不是function, 就原樣返回value或者reason.
        const fulFilledFn = this.isFunction(onFulfilled) ? onFulfilled : (value) => {
            return value;
        }
        const rejectedFn = this.isFunction(onRejected) ? onRejected : (reason) => {
            throw reason;
        };

        // 如果 onFulfilled 不是函數(shù)且 promise1 成功執(zhí)行矮嫉, promise2 必須成功執(zhí)行并返回相同的值
        const fulFilledFnWithCatch = (resolve, reject, newPromise) => {
            // onFulfilled 和 onRejected 是微任務(wù),咱們可以用queueMicrotask包裹執(zhí)行函數(shù)
            queueMicrotask(() => {
                try {
                    if (!this.isFunction(onFulfilled)) {
                        resolve(this.value);
                    } else {
                        // 如果 onFulfilled 或者 onRejected 返回一個值 x 喳篇,則運行resolvePromise方法
                        const x = fulFilledFn(this.value);
                        this.resolvePromise(newPromise, x, resolve, reject);
                    }
                // onFulfilled 或者 onRejected 拋出一個異常 e 敞临,則 promise2 必須拒絕執(zhí)行,并返回拒因 e麸澜。
                } catch (e) {
                    reject(e)
                }
            })
        };
        // 如果 onRejected 不是函數(shù)且 promise1 拒絕執(zhí)行挺尿, promise2 必須拒絕執(zhí)行并返回相同的據(jù)因。
        // 如果promise1的onRejected執(zhí)行成功了炊邦,promise2應(yīng)該被resolve
        const rejectedFnWithCatch = (resolve, reject, newPromise) => {
            queueMicrotask(() => {
                try {
                    if (!this.isFunction(onRejected)) {
                        reject(this.reason);
                    } else {
                        const x = rejectedFn(this.reason);
                        this.resolvePromise(newPromise, x, resolve, reject);
                    }
                } catch (e) {
                    reject(e);
                }
            })
        }

        // 根據(jù)當前promise的狀態(tài), 調(diào)用不同的函數(shù)编矾,then的返回值是一個promise
        switch (this.status) {
            case FULFILLED: {
                const newPromise = new MPromise((resolve, reject) => fulFilledFnWithCatch(resolve, reject, newPromise));
                return newPromise;
            }
            case REJECTED: {
                const newPromise = new MPromise((resolve, reject) => rejectedFnWithCatch(resolve, reject, newPromise));
                return newPromise;
            }
            case PENDING: {
                const newPromise = new MPromise((resolve, reject) => {
                    this.FULFILLED_CALLBACK_LIST.push(() => fulFilledFnWithCatch(resolve, reject, newPromise));
                    this.REJECTED_CALLBACK_LIST.push(() => rejectedFnWithCatch(resolve, reject, newPromise));
                });
                return newPromise;
            }
        }
    }
  • 實現(xiàn)resolvePromise
    resolvePromise(newPromise, x, resolve, reject) {
        // 如果 newPromise 和 x 指向同一對象,以 TypeError 為據(jù)因拒絕執(zhí)行 newPromise
        // 這是為了防止死循環(huán)
        if (newPromise === x) {
            return reject(new TypeError('The promise and the return value are the same'));
        }

        if (x instanceof MPromise) {
            // 如果 x 為 Promise 馁害,則使 newPromise 接受 x 的狀態(tài)
            // 也就是繼續(xù)執(zhí)行x窄俏,如果執(zhí)行的時候拿到一個y,還要繼續(xù)解析y
            x.then((y) => {
                this.resolvePromise(newPromise, y, resolve, reject);
            }, reject);
        } else if (typeof x === 'object' || this.isFunction(x)) {
            // 如果 x 為對象或者函數(shù)
            if (x === null) {
                // null也會被判斷為對象
                return resolve(x);
            }

            let then = null;

            try {
                // 把 x.then 賦值給 then 
                then = x.then;
            } catch (error) {
                // 如果取 x.then 的值時拋出錯誤 e 碘菜,則以 e 為據(jù)因拒絕 promise
                return reject(error);
            }

            // 如果 then 是函數(shù)
            if (this.isFunction(then)) {
                let called = false;
                // 將 x 作為函數(shù)的作用域 this 調(diào)用
                // 傳遞兩個回調(diào)函數(shù)作為參數(shù)凹蜈,第一個參數(shù)叫做 resolvePromise ,第二個參數(shù)叫做 rejectPromise
                try {
                    then.call(
                        x,
                        // 如果 resolvePromise 以值 y 為參數(shù)被調(diào)用忍啸,則運行 resolvePromise
                        (y) => {
                            // 需要有一個變量called來保證只調(diào)用一次.
                            if (called) return;
                            called = true;
                            this.resolvePromise(newPromise, y, resolve, reject);
                        },
                        // 如果 rejectPromise 以據(jù)因 r 為參數(shù)被調(diào)用仰坦,則以據(jù)因 r 拒絕 promise
                        (r) => {
                            if (called) return;
                            called = true;
                            reject(r);
                        });
                } catch (error) {
                    // 如果調(diào)用 then 方法拋出了異常 e:
                    if (called) return;

                    // 否則以 e 為據(jù)因拒絕 promise
                    reject(error);
                }
            } else {
                // 如果 then 不是函數(shù),以 x 為參數(shù)執(zhí)行 promise
                resolve(x);
            }
        } else {
            // 如果 x 不為對象或者函數(shù)计雌,以 x 為參數(shù)執(zhí)行 promise
            resolve(x);
        }
    }
  • 其他方法定義
  // catch方法悄晃,使得可以調(diào)用.catch
    catch (onRejected) {
        return this.then(null, onRejected);
    }

    isFunction(param) {
        return typeof param === 'function';
    }
  • 靜態(tài)方法實現(xiàn)
    // 將現(xiàn)有對象轉(zhuǎn)為Promise對象
    static resolve(value) {
        if (value instanceof MPromise) {
            return value;
        }
        // 如果 Promise.resolve 方法的參數(shù),不是具有 then 方法的對象(又稱 thenable 對象)凿滤,則返回一個新的 Promise 對象妈橄,且它的狀態(tài)為fulfilled庶近。
        return new MPromise((resolve) => {
            resolve(value);
        });
    }

    static reject(reason) {
        // 返回一個新的Promise實例,該實例的狀態(tài)為rejected眷蚓。Promise.reject方法的參數(shù)reason鼻种,會被傳遞給實例的回調(diào)函數(shù)。
        return new MPromise((resolve, reject) => {
            reject(reason);
        });
    }

    `const p = Promise.race([p1, p2, p3]);`
    // 該方法是將多個 Promise 實例溪椎,包裝成一個新的 Promise 實例普舆。
    // 只要p1、p2校读、p3之中有一個實例率先改變狀態(tài),p的狀態(tài)就跟著改變祖能。那個率先改變的 Promise 實例的返回值歉秫,就傳遞給p的回調(diào)函數(shù)。
    static race(promiseList) {
        return new MPromise((resolve, reject) => {
            const length = promiseList.length;

            if (length === 0) {
                return resolve();
            } else {
                for (let i = 0; i < length; i++) {
                    MPromise.resolve(promiseList[i]).then(
                        (value) => {
                            return resolve(value);
                        },
                        (reason) => {
                            return reject(reason);
                        });
                }
            }
        });

    }
  • 測試代碼
const test = new MPromise((resolve, reject) => {
    setTimeout(() => {
        resolve(111);
    }, 1000);
}).then(console.log); // 111

console.log(test);
// MPromise {
//     FULFILLED_CALLBACK_LIST: [],
//     REJECTED_CALLBACK_LIST: [],
//     _status: 'pending',
//     value: null,
//     reason: null
//   }

setTimeout(() => {
    console.log(test);
    // MPromise {
    //     FULFILLED_CALLBACK_LIST: [],
    //     REJECTED_CALLBACK_LIST: [],
    //     _status: 'fulfilled',
    //     value: undefined,
    //     reason: null
    //   }
}, 2000)

二养铸、迭代器和生成器

迭代器Iterator

迭代器Iterator 是 ES6 引入的一種新的遍歷機制雁芙,同時也是一種特殊對象,它具有一些專門為迭代過程設(shè)計的專有接口钞螟。
每個迭代器對象都有一個next()方法兔甘,每次調(diào)用都返回一個當前結(jié)果對象。當前結(jié)果對象中有兩個屬性:

  1. value:當前屬性的值
  2. done:用于判斷是否遍歷結(jié)束鳞滨,當沒有更多可返回的數(shù)據(jù)時洞焙,返回true

每調(diào)用一次next()方法,都會返回下一個可用的值拯啦,直到遍歷結(jié)束澡匪。

生成器generator

生成器是一種返回迭代器的函數(shù),通過function關(guān)鍵字后的星號(*)來表示褒链,函數(shù)中會用到新的關(guān)鍵字yield唁情。星號可以緊挨著function關(guān)鍵字,也可以在中間添加一個空格.

  1. 每當執(zhí)行完一條yield語句后函數(shù)就會自動停止執(zhí)行, 直到再次調(diào)用next();
  2. yield關(guān)鍵字只可在生成器內(nèi)部使用甫匹,在其他地方使用會導致程序拋出錯誤;
  3. 可以通過函數(shù)表達式來創(chuàng)建生成器, 但是不能使用箭頭函數(shù)
    let generator = function *(){}
function* generator() {
    const list = [1, 2, 3];
    for (let i of list) {
        yield i;
    }
}


let g = generator();

console.log(g.next()); // {value: 1, done: false}
console.log(g.next()); // {value: 2, done: false}
console.log(g.next()); // {value: 3, done: false}
console.log(g.next()); // {value: undefined, done: true}

三甸鸟、利用Async封裝一個函數(shù), 能夠讓generator自動執(zhí)行到完畢

function longTimeFn(time) {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve(time);
        }, time);
    })
};

function asyncFunc(generator) {
    const iterator = generator(); // 接下來要執(zhí)行next
    // data為第一次執(zhí)行之后的返回結(jié)果,用于傳給第二次執(zhí)行
    const next = (data) => {
        const {
            value,
            done
        } = iterator.next(data); // 第二次執(zhí)行兵迅,并接收第一次的請求結(jié)果 value 和 done

        if (done) return; // 執(zhí)行完畢, 直接返回
        // 第一次執(zhí)行next時抢韭,yield返回的 promise實例 賦值給了 value
        value.then(data => {
            next(data); // 當?shù)谝淮蝪alue 執(zhí)行完畢且成功時,執(zhí)行下一步(并把第一次的結(jié)果傳遞下一步)
        });
    }
    next();
};

asyncFunc(function* () {
    let data = yield longTimeFn(1000);
    console.log(data);
    data = yield longTimeFn(2000);
    console.log(data);
    return data;
})
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末喷兼,一起剝皮案震驚了整個濱河市篮绰,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌季惯,老刑警劉巖吠各,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件臀突,死亡現(xiàn)場離奇詭異,居然都是意外死亡贾漏,警方通過查閱死者的電腦和手機候学,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來纵散,“玉大人梳码,你說我怎么就攤上這事∥橄疲” “怎么了掰茶?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長蜜笤。 經(jīng)常有香客問我濒蒋,道長,這世上最難降的妖魔是什么把兔? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任沪伙,我火速辦了婚禮,結(jié)果婚禮上县好,老公的妹妹穿的比我還像新娘围橡。我一直安慰自己,他們只是感情好缕贡,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布翁授。 她就那樣靜靜地躺著,像睡著了一般善绎。 火紅的嫁衣襯著肌膚如雪黔漂。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天禀酱,我揣著相機與錄音炬守,去河邊找鬼。 笑死剂跟,一個胖子當著我的面吹牛减途,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播曹洽,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼鳍置,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了送淆?” 一聲冷哼從身側(cè)響起税产,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后辟拷,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體撞羽,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年衫冻,在試婚紗的時候發(fā)現(xiàn)自己被綠了诀紊。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡隅俘,死狀恐怖邻奠,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情为居,我是刑警寧澤碌宴,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站颜骤,受9級特大地震影響唧喉,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜忍抽,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望董朝。 院中可真熱鬧鸠项,春花似錦、人聲如沸子姜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽哥捕。三九已至牧抽,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間遥赚,已是汗流浹背扬舒。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留凫佛,地道東北人讲坎。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像愧薛,于是被迫代替她去往敵國和親晨炕。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容