国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

30分鐘,讓你徹底明白Promise原理

Profeel / 914人閱讀

摘要:鏈?zhǔn)绞侵冈诋?dāng)前達(dá)到狀態(tài)后,即開始進(jìn)行下一個(gè)后鄰。在中發(fā)現(xiàn)沒有指定異步操作失敗的回調(diào)時(shí),會(huì)直接將函數(shù)返回的,后同設(shè)為狀態(tài),如此達(dá)成執(zhí)行后續(xù)失敗回調(diào)的效果。

原文鏈接

前言

前一陣子記錄了promise的一些常規(guī)用法,這篇文章再深入一個(gè)層次,來分析分析promise的這種規(guī)則機(jī)制是如何實(shí)現(xiàn)的。ps:本文適合已經(jīng)對(duì)promise的用法有所了解的人閱讀,如果對(duì)其用法還不是太了解,可以移步我的上一篇博文。

本文的promise源碼是按照Promise/A+規(guī)范來編寫的(不想看英文版的移步Promise/A+規(guī)范中文翻譯)

引子

為了讓大家更容易理解,我們從一個(gè)場(chǎng)景開始講解,讓大家一步一步跟著思路思考,相信你一定會(huì)更容易看懂。

考慮下面一種獲取用戶id的請(qǐng)求處理

//例1
function getUserId() {
    return new Promise(function(resolve) {
        //異步請(qǐng)求
        http.get(url, function(results) {
            resolve(results.id)
        })
    })
}

getUserId().then(function(id) {
    //一些處理
})

getUserId方法返回一個(gè)promise,可以通過它的then方法注冊(cè)(注意注冊(cè)這個(gè)詞)在promise異步操作成功時(shí)執(zhí)行的回調(diào)。這種執(zhí)行方式,使得異步調(diào)用變得十分順手。

原理剖析

那么類似這種功能的Promise怎么實(shí)現(xiàn)呢?其實(shí)按照上面一句話,實(shí)現(xiàn)一個(gè)最基礎(chǔ)的雛形還是很easy的。

極簡(jiǎn)promise雛形
function Promise(fn) {
    var value = null,
        callbacks = [];  //callbacks為數(shù)組,因?yàn)榭赡芡瑫r(shí)有很多個(gè)回調(diào)

    this.then = function (onFulfilled) {
        callbacks.push(onFulfilled);
    };

    function resolve(value) {
        callbacks.forEach(function (callback) {
            callback(value);
        });
    }

    fn(resolve);
}

上述代碼很簡(jiǎn)單,大致的邏輯是這樣的:

調(diào)用then方法,將想要在Promise異步操作成功時(shí)執(zhí)行的回調(diào)放入callbacks隊(duì)列,其實(shí)也就是注冊(cè)回調(diào)函數(shù),可以向觀察者模式方向思考;

創(chuàng)建Promise實(shí)例時(shí)傳入的函數(shù)會(huì)被賦予一個(gè)函數(shù)類型的參數(shù),即resolve,它接收一個(gè)參數(shù)value,代表異步操作返回的結(jié)果,當(dāng)一步操作執(zhí)行成功后,用戶會(huì)調(diào)用resolve方法,這時(shí)候其實(shí)真正執(zhí)行的操作是將callbacks隊(duì)列中的回調(diào)一一執(zhí)行;

可以結(jié)合例1中的代碼來看,首先new Promise時(shí),傳給promise的函數(shù)發(fā)送異步請(qǐng)求,接著調(diào)用promise對(duì)象的then屬性,注冊(cè)請(qǐng)求成功的回調(diào)函數(shù),然后當(dāng)異步請(qǐng)求發(fā)送成功時(shí),調(diào)用resolve(results.id)方法, 該方法執(zhí)行then方法注冊(cè)的回調(diào)數(shù)組。

相信仔細(xì)的人應(yīng)該可以看出來,then方法應(yīng)該能夠鏈?zhǔn)秸{(diào)用,但是上面的最基礎(chǔ)簡(jiǎn)單的版本顯然無法支持鏈?zhǔn)秸{(diào)用。想讓then方法支持鏈?zhǔn)秸{(diào)用,其實(shí)也是很簡(jiǎn)單的:

this.then = function (onFulfilled) {
    callbacks.push(onFulfilled);
    return this;
};

see?只要簡(jiǎn)單一句話就可以實(shí)現(xiàn)類似下面的鏈?zhǔn)秸{(diào)用:

// 例2
getUserId().then(function (id) {
    // 一些處理
}).then(function (id) {
    // 一些處理
});
加入延時(shí)機(jī)制

細(xì)心的同學(xué)應(yīng)該發(fā)現(xiàn),上述代碼可能還存在一個(gè)問題:如果在then方法注冊(cè)回調(diào)之前,resolve函數(shù)就執(zhí)行了,怎么辦?比如promise內(nèi)部的函數(shù)是同步函數(shù):

// 例3
function getUserId() {
    return new Promise(function (resolve) {
        resolve(9876);
    });
}
getUserId().then(function (id) {
    // 一些處理
});

這顯然是不允許的,Promises/A+規(guī)范明確要求回調(diào)需要通過異步方式執(zhí)行,用以保證一致可靠的執(zhí)行順序。因此我們要加入一些處理,保證在resolve執(zhí)行之前,then方法已經(jīng)注冊(cè)完所有的回調(diào)。我們可以這樣改造下resolve函數(shù):

function resolve(value) {
    setTimeout(function() {
        callbacks.forEach(function (callback) {
            callback(value);
        });
    }, 0)
} 

上述代碼的思路也很簡(jiǎn)單,就是通過setTimeout機(jī)制,將resolve中執(zhí)行回調(diào)的邏輯放置到JS任務(wù)隊(duì)列末尾,以保證在resolve執(zhí)行時(shí),then方法的回調(diào)函數(shù)已經(jīng)注冊(cè)完成.

但是,這樣好像還存在一個(gè)問題,可以細(xì)想一下:如果Promise異步操作已經(jīng)成功,這時(shí),在異步操作成功之前注冊(cè)的回調(diào)都會(huì)執(zhí)行,但是在Promise異步操作成功這之后調(diào)用的then注冊(cè)的回調(diào)就再也不會(huì)執(zhí)行了,這顯然不是我們想要的。

加入狀態(tài)

恩,為了解決上一節(jié)拋出的問題,我們必須加入狀態(tài)機(jī)制,也就是大家熟知的pendingfulfilledrejected

Promises/A+規(guī)范中的2.1Promise States中明確規(guī)定了,pending可以轉(zhuǎn)化為fulfilledrejected并且只能轉(zhuǎn)化一次,也就是說如果pending轉(zhuǎn)化到fulfilled狀態(tài),那么就不能再轉(zhuǎn)化到rejected。并且fulfilledrejected狀態(tài)只能由pending轉(zhuǎn)化而來,兩者之間不能互相轉(zhuǎn)換。一圖勝千言:

改進(jìn)后的代碼是這樣的:

function Promise(fn) {
    var state = "pending",
        value = null,
        callbacks = [];

    this.then = function (onFulfilled) {
        if (state === "pending") {
            callbacks.push(onFulfilled);
            return this;
        }
        onFulfilled(value);
        return this;
    };

    function resolve(newValue) {
        value = newValue;
        state = "fulfilled";
        setTimeout(function () {
            callbacks.forEach(function (callback) {
                callback(value);
            });
        }, 0);
    }

    fn(resolve);
}

上述代碼的思路是這樣的:resolve執(zhí)行時(shí),會(huì)將狀態(tài)設(shè)置為fulfilled,在此之后調(diào)用then添加的新回調(diào),都會(huì)立即執(zhí)行。

這里沒有任何地方將state設(shè)為rejected,為了讓大家聚焦在核心代碼上,這個(gè)問題后面會(huì)有一小節(jié)專門加入。

鏈?zhǔn)絇romise

那么這里問題又來了,如果用戶再then函數(shù)里面注冊(cè)的仍然是一個(gè)Promise,該如何解決?比如下面的例4

// 例4
getUserId()
    .then(getUserJobById)
    .then(function (job) {
        // 對(duì)job的處理
    });

function getUserJobById(id) {
    return new Promise(function (resolve) {
        http.get(baseUrl + id, function(job) {
            resolve(job);
        });
    });
}

這種場(chǎng)景相信用過promise的人都知道會(huì)有很多,那么類似這種就是所謂的鏈?zhǔn)?b>Promise。

鏈?zhǔn)?b>Promise是指在當(dāng)前promise達(dá)到fulfilled狀態(tài)后,即開始進(jìn)行下一個(gè)promise(后鄰promise)。那么我們?nèi)绾毋暯赢?dāng)前promise和后鄰promise呢?(這是這里的難點(diǎn))。

其實(shí)也不是辣么難,只要在then方法里面return一個(gè)promise就好啦。Promises/A+規(guī)范中的2.2.7就是這么說噠(微笑臉)~

下面來看看這段暗藏玄機(jī)的then方法和resolve方法改造代碼:

function Promise(fn) {
    var state = "pending",
        value = null,
        callbacks = [];

    this.then = function (onFulfilled) {
        return new Promise(function (resolve) {
            handle({
                onFulfilled: onFulfilled || null,
                resolve: resolve
            });
        });
    };

    function handle(callback) {
        if (state === "pending") {
            callbacks.push(callback);
            return;
        }
        //如果then中沒有傳遞任何東西
        if(!callback.onFulfilled) {
            callback.resolve(value);
            return;
        }

        var ret = callback.onFulfilled(value);
        callback.resolve(ret);
    }

    
    function resolve(newValue) {
        if (newValue && (typeof newValue === "object" || typeof newValue === "function")) {
            var then = newValue.then;
            if (typeof then === "function") {
                then.call(newValue, resolve);
                return;
            }
        }
        state = "fulfilled";
        value = newValue;
        setTimeout(function () {
            callbacks.forEach(function (callback) {
                handle(callback);
            });
        }, 0);
    }

    fn(resolve);
}

我們結(jié)合例4的代碼,分析下上面的代碼邏輯,為了方便閱讀,我把例4的代碼貼在這里:

// 例4
getUserId()
    .then(getUserJobById)
    .then(function (job) {
        // 對(duì)job的處理
    });

function getUserJobById(id) {
    return new Promise(function (resolve) {
        http.get(baseUrl + id, function(job) {
            resolve(job);
        });
    });
}

then方法中,創(chuàng)建并返回了新的Promise實(shí)例,這是串行Promise的基礎(chǔ),并且支持鏈?zhǔn)秸{(diào)用。

handle方法是promise內(nèi)部的方法。then方法傳入的形參onFulfilled以及創(chuàng)建新Promise實(shí)例時(shí)傳入的resolve均被push到當(dāng)前promisecallbacks隊(duì)列中,這是銜接當(dāng)前promise和后鄰promise的關(guān)鍵所在(這里一定要好好的分析下handle的作用)。

getUserId生成的promise(簡(jiǎn)稱getUserId promise)異步操作成功,執(zhí)行其內(nèi)部方法resolve,傳入的參數(shù)正是異步操作的結(jié)果id

調(diào)用handle方法處理callbacks隊(duì)列中的回調(diào):getUserJobById方法,生成新的promisegetUserJobById promise

執(zhí)行之前由getUserId promisethen方法生成的新promise(稱為bridge promise)的resolve方法,傳入?yún)?shù)為getUserJobById promise。這種情況下,會(huì)將該resolve方法傳入getUserJobById promisethen方法中,并直接返回。

getUserJobById promise異步操作成功時(shí),執(zhí)行其callbacks中的回調(diào):getUserId bridge promise中的resolve方法

最后執(zhí)行getUserId bridge promise的后鄰promisecallbacks中的回調(diào)。

更直白的可以看下面的圖,一圖勝千言(都是根據(jù)自己的理解畫出來的,如有不對(duì)歡迎指正):

失敗處理

在異步操作失敗時(shí),標(biāo)記其狀態(tài)為rejected,并執(zhí)行注冊(cè)的失敗回調(diào):

//例5
function getUserId() {
    return new Promise(function(resolve) {
        //異步請(qǐng)求
        http.get(url, function(error, results) {
            if (error) {
                reject(error);
            }
            resolve(results.id)
        })
    })
}

getUserId().then(function(id) {
    //一些處理
}, function(error) {
    console.log(error)
})

有了之前處理fulfilled狀態(tài)的經(jīng)驗(yàn),支持錯(cuò)誤處理變得很容易,只需要在注冊(cè)回調(diào)、處理狀態(tài)變更上都要加入新的邏輯:

function Promise(fn) {
    var state = "pending",
        value = null,
        callbacks = [];

    this.then = function (onFulfilled, onRejected) {
        return new Promise(function (resolve, reject) {
            handle({
                onFulfilled: onFulfilled || null,
                onRejected: onRejected || null,
                resolve: resolve,
                reject: reject
            });
        });
    };

    function handle(callback) {
        if (state === "pending") {
            callbacks.push(callback);
            return;
        }

        var cb = state === "fulfilled" ? callback.onFulfilled : callback.onRejected,
            ret;
        if (cb === null) {
            cb = state === "fulfilled" ? callback.resolve : callback.reject;
            cb(value);
            return;
        }
        ret = cb(value);
        callback.resolve(ret);
    }

    function resolve(newValue) {
        if (newValue && (typeof newValue === "object" || typeof newValue === "function")) {
            var then = newValue.then;
            if (typeof then === "function") {
                then.call(newValue, resolve, reject);
                return;
            }
        }
        state = "fulfilled";
        value = newValue;
        execute();
    }

    function reject(reason) {
        state = "rejected";
        value = reason;
        execute();
    }

    function execute() {
        setTimeout(function () {
            callbacks.forEach(function (callback) {
                handle(callback);
            });
        }, 0);
    }

    fn(resolve, reject);
}

上述代碼增加了新的reject方法,供異步操作失敗時(shí)調(diào)用,同時(shí)抽出了resolvereject共用的部分,形成execute方法。

錯(cuò)誤冒泡是上述代碼已經(jīng)支持,且非常實(shí)用的一個(gè)特性。在handle中發(fā)現(xiàn)沒有指定異步操作失敗的回調(diào)時(shí),會(huì)直接將bridge promise(then函數(shù)返回的promise,后同)設(shè)為rejected狀態(tài),如此達(dá)成執(zhí)行后續(xù)失敗回調(diào)的效果。這有利于簡(jiǎn)化串行Promise的失敗處理成本,因?yàn)橐唤M異步操作往往會(huì)對(duì)應(yīng)一個(gè)實(shí)際功能,失敗處理方法通常是一致的:

//例6
getUserId()
    .then(getUserJobById)
    .then(function (job) {
        // 處理job
    }, function (error) {
        // getUserId或者getUerJobById時(shí)出現(xiàn)的錯(cuò)誤
        console.log(error);
    });
異常處理

細(xì)心的同學(xué)會(huì)想到:如果在執(zhí)行成功回調(diào)、失敗回調(diào)時(shí)代碼出錯(cuò)怎么辦?對(duì)于這類異常,可以使用try-catch捕獲錯(cuò)誤,并將bridge promise設(shè)為rejected狀態(tài)。handle方法改造如下:

function handle(callback) {
    if (state === "pending") {
        callbacks.push(callback);
        return;
    }

    var cb = state === "fulfilled" ? callback.onFulfilled : callback.onRejected,
        ret;
    if (cb === null) {
        cb = state === "fulfilled" ? callback.resolve : callback.reject;
        cb(value);
        return;
    }
    try {
        ret = cb(value);
        callback.resolve(ret);
    } catch (e) {
        callback.reject(e);
    } 
}

如果在異步操作中,多次執(zhí)行resolve或者reject會(huì)重復(fù)處理后續(xù)回調(diào),可以通過內(nèi)置一個(gè)標(biāo)志位解決。

總結(jié)

剛開始看promise源碼的時(shí)候總不能很好的理解then和resolve函數(shù)的運(yùn)行機(jī)理,但是如果你靜下心來,反過來根據(jù)執(zhí)行promise時(shí)的邏輯來推演,就不難理解了。這里一定要注意的點(diǎn)是:promise里面的then函數(shù)僅僅是注冊(cè)了后續(xù)需要執(zhí)行的代碼,真正的執(zhí)行是在resolve方法里面執(zhí)行的,理清了這層,再來分析源碼會(huì)省力的多。

現(xiàn)在回顧下Promise的實(shí)現(xiàn)過程,其主要使用了設(shè)計(jì)模式中的觀察者模式:

通過Promise.prototype.then和Promise.prototype.catch方法將觀察者方法注冊(cè)到被觀察者Promise對(duì)象中,同時(shí)返回一個(gè)新的Promise對(duì)象,以便可以鏈?zhǔn)秸{(diào)用。

被觀察者管理內(nèi)部pending、fulfilled和rejected的狀態(tài)轉(zhuǎn)變,同時(shí)通過構(gòu)造函數(shù)中傳遞的resolve和reject方法以主動(dòng)觸發(fā)狀態(tài)轉(zhuǎn)變和通知觀察者。

參考文獻(xiàn)

深入理解 Promise
JavaScript Promises ... In Wicked Detail

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/83085.html

相關(guān)文章

  • JavasScript重難點(diǎn)知識(shí)

    摘要:忍者級(jí)別的函數(shù)操作對(duì)于什么是匿名函數(shù),這里就不做過多介紹了。我們需要知道的是,對(duì)于而言,匿名函數(shù)是一個(gè)很重要且具有邏輯性的特性。通常,匿名函數(shù)的使用情況是創(chuàng)建一個(gè)供以后使用的函數(shù)。 JS 中的遞歸 遞歸, 遞歸基礎(chǔ), 斐波那契數(shù)列, 使用遞歸方式深拷貝, 自定義事件添加 這一次,徹底弄懂 JavaScript 執(zhí)行機(jī)制 本文的目的就是要保證你徹底弄懂javascript的執(zhí)行機(jī)制,如果...

    forsigner 評(píng)論0 收藏0
  • 前端基礎(chǔ)

    摘要:談起閉包,它可是兩個(gè)核心技術(shù)之一異步基于打造前端持續(xù)集成開發(fā)環(huán)境本文將以一個(gè)標(biāo)準(zhǔn)的項(xiàng)目為例,完全拋棄傳統(tǒng)的前端項(xiàng)目開發(fā)部署方式,基于容器技術(shù)打造一個(gè)精簡(jiǎn)的前端持續(xù)集成的開發(fā)環(huán)境。 這一次,徹底弄懂 JavaScript 執(zhí)行機(jī)制 本文的目的就是要保證你徹底弄懂javascript的執(zhí)行機(jī)制,如果讀完本文還不懂,可以揍我。 不論你是javascript新手還是老鳥,不論是面試求職,還是日...

    graf 評(píng)論0 收藏0
  • 異步讀取文件的幾種姿勢(shì)

    摘要:臆想的針對(duì)讀取到的內(nèi)容進(jìn)行操作,比如打印文件內(nèi)容臆想中,讀取文件是有返回值的,將返回值,即文件內(nèi)容,賦給一個(gè)變量,然后決定對(duì)讀取到的內(nèi)容進(jìn)行相應(yīng)的操作,例如打印文件中的內(nèi)容。 臆想的 let fs = require(fs) function readFile(filename){ ... } let content = readFile(config.js) // 針對(duì)讀...

    chinafgj 評(píng)論0 收藏0
  • ES6-7

    摘要:的翻譯文檔由的維護(hù)很多人說,阮老師已經(jīng)有一本關(guān)于的書了入門,覺得看看這本書就足夠了。前端的異步解決方案之和異步編程模式在前端開發(fā)過程中,顯得越來越重要。為了讓編程更美好,我們就需要引入來降低異步編程的復(fù)雜性。 JavaScript Promise 迷你書(中文版) 超詳細(xì)介紹promise的gitbook,看完再不會(huì)promise...... 本書的目的是以目前還在制定中的ECMASc...

    mudiyouyou 評(píng)論0 收藏0
  • Promise之深入淺出

    摘要:不兼容問題,本文不予以處理,出門左轉(zhuǎn),找谷哥。如果中的回調(diào)函數(shù)拋出一個(gè)錯(cuò)誤,那么返回的將會(huì)成為拒絕狀態(tài),并且將拋出的錯(cuò)誤作為拒絕狀態(tài)的回調(diào)函數(shù)的參數(shù)值。 Promise與async 主要內(nèi)容: promise基本實(shí)現(xiàn)原理 promise 使用中難點(diǎn)(鏈?zhǔn)秸{(diào)用,API基本上返回都是一個(gè)新Promise,及參數(shù)傳遞) promise 對(duì)異常處理 參考: ? 30分鐘,讓你徹底明...

    littleGrow 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<