摘要:這樣得到權(quán)力回調(diào)函數(shù),當?shù)漠惒酱a執(zhí)行完畢后,由來執(zhí)行回調(diào)函數(shù)。而在平時的開發(fā)過程中,在異步編程中起到了幾乎不可替代的作用。
其實Promise本身并不具備異步的能力,而之所以這里需要多帶帶開一篇說明其原理,是因為Promise在異步編程的過程中是一個不可或缺的一環(huán)。原因下面細說。
在說promise之前,有必要先說下JS中的回調(diào)方式。比如下面:
function doSomethingAfterTime(time, something) { setTimeout(fun, time); }
但是這樣的回調(diào)方式有一個問題,可讀性太差。另外當回調(diào)的層次多了以后,容易陷入回調(diào)地獄。舉個例子:
function func1(cb){ // do something cb(); } function func2(cb){ // do something cb(); } function func3(cb){ // do something cb(); } // do func1(function(){ func2(function(){ func3(function(){ }); }); });
這樣的代碼讀起來簡直就是折磨,暈死!
下面試著改進下代碼,試著將回調(diào)函數(shù)封裝起來。順便剖析下promise的原理。
Promise的原理先來一個最簡單的。
function Promise(something){ var callback = null; this.then = function(onCompelete){ callback = onCompelete; }; something(function (value){ callback(value); }); }
下面是調(diào)用代碼。
// 事件1 function func1(){ return new Promise(function(resolve){ // do something setTimeout(function(){ console.log("func1"); resolve(); },1000); }); } func1().then(function(){ console.log("func1 complete"); });
上面對Promise的封裝算是最簡單的版本,只是模擬了Promise的調(diào)用方法,比如then,還有Promise的構(gòu)造函數(shù)。但是這樣的封裝無法實現(xiàn)鏈式調(diào)用,鏈式調(diào)用的核心就是當調(diào)用某個方法的時候返回該對象本身或者該對象對應(yīng)class的全新對象。而對于Promise的改造也很簡單.
then方法返回Promise本身
Promoise需要支持多個callback。
function Promise(something){ var callbacks = []; this.then = function(onCompelete){ callbacks.push(onCompelete); return this; }; function resolve(value){ callbacks.forEach(function(cb){ cb(value); }); } something(resolve); }
調(diào)用代碼如下:
func1().then(function(){ console.log("func1 complete"); }).then(function(){ console.log("then2"); }).then(function(){ console.log("then3"); });
現(xiàn)在的Promise執(zhí)行上面的代碼后能夠得到正確的執(zhí)行結(jié)果,但是有一個問題,如果我們想在then方法再調(diào)用一個返回promise的方法?比如這樣:
// 事件2 function func2(){ return new Promise(function(resolve){ // do something setTimeout(function(){ console.log("func2"); resolve(); },1000); }); } func1().then(func2).then(function(){ console.log("all complete"); });
輸出如下:
func1 all complete func2
你會發(fā)現(xiàn)雖然func2成功調(diào)用了,但是輸出順序亂了,我們期望的正確輸出順序應(yīng)該是:
func1 func2 all complete
分析下問題出在哪里?問題就出在Promise中的callbacks,第一個then是在func1返回的Promise上調(diào)用的,而第二個then事實上還是在func1返回的Promise上調(diào)用的。然而我們希望的是,第二個then應(yīng)該是在func2返回的Promise調(diào)用,這時候就需要考慮如何進一步改造Promise了。
對于then傳入的onCompelete函數(shù)參數(shù),它是不知道這個函數(shù)具體是否會返回Promise,只有調(diào)用了onCompelete方法才能知道具體返回的數(shù)據(jù)。但是onCompelete是回調(diào)函數(shù),你無法直接在then中調(diào)用。因此需要考慮其他的方式。
如果then方法里面返回一個新的Promise對象呢?用這個新的Promise作為中間代理,比如這樣:
function Promise(something){ var callbacks = []; this.then = function(onCompelete){ return new Promise(function (resolve) { callbacks.push({ onCompelete: onCompelete, resolve: resolve }); }); }; function resolve(value){ callbacks.forEach(function(cb){ var ret = cb.onCompelete(value); cb.resolve(ret); }) } something(resolve); }
但是運行的時候你會發(fā)現(xiàn)輸出順序還是沒變,還是有問題的。那么繼續(xù)分析問題出在哪里?
通過調(diào)試發(fā)現(xiàn),resolve傳入的value有可能是promise對象,而我們已經(jīng)在then方法里面返回了新的promise對象了,交由該對象作為代理了。因此resolve傳入的value如果是promise對象的話,那么就需要把當前promise的resolve處理權(quán)交出去,交給傳入的promise對象。相當于代理人把權(quán)力交還給實際應(yīng)該處理的對象。可能有點繞,我再詳細的描述下
func1返回的promise為p1,then返回的promise為p2,resolve傳入的promise對象為p3,func2返回的promise對象為p4。上面一共提到4個promise對象。
說下描說下調(diào)用順序。
首先由func1創(chuàng)建p1,然后調(diào)用then方法創(chuàng)建了p2,然后再次調(diào)用了then方法,由p2創(chuàng)建了p3。p2和p3都是由then創(chuàng)建的代理人。
這時候func1中的異步代碼執(zhí)行了,1秒過后由func1調(diào)用了p1的resolve方法,并且將callbacks數(shù)組內(nèi)的方法依次調(diào)用,然后由cb.onCompelete(value)方法間接得到func2返回的p4,接著調(diào)用p2的resolve方法將p4傳入。但是上面說了,p2只是個代理,應(yīng)該把權(quán)力交還給p4來執(zhí)行。這樣p4得到權(quán)力--回調(diào)函數(shù),當func2的異步代碼執(zhí)行完畢后,由p4來執(zhí)行回調(diào)函數(shù)。
因此resolve方法需要進行如下改造。
function resolve(value) { // 交還權(quán)力,并且把resolve傳過去 if (value && (typeof value.then === "function")) { value.then.call(value, resolve); return; } callbacks.forEach(function (cb) { var ret = cb.onCompelete(value); cb.resolve(ret); }); }
上面的代碼就是交權(quán)的代碼。這樣完全的Promise修改如下:
function Promise(something) { var callbacks = []; this.then = function (onCompelete) { return new Promise(function (resolve) { callbacks.push({ onCompelete: onCompelete, resolve: resolve }); }); }; function resolve(value) { if (value && (typeof value.then === "function")) { value.then.call(value, resolve); return; } callbacks.forEach(function (cb) { var ret = cb.onCompelete(value); cb.resolve(ret); }); } something(resolve); }
這樣修改過后,再執(zhí)行如下代碼:
func1().then(func2).then(function () { console.log("all complete"); });
現(xiàn)在就能得到正確的執(zhí)行結(jié)果了。
至此,一個簡單的Promise定義完了。這時候有一個問題,如果調(diào)用then方法之前resolve已經(jīng)被執(zhí)行了怎么辦呢,豈不是永遠都得不到回調(diào)了?比如這樣:
(new Promise(function (resolve) { resolve(); })).then(function(){ console.log("complete"); });
你會發(fā)現(xiàn)then里面的回調(diào)就不會執(zhí)行了。其實這時候只需要做一個小小的改動就行了。改造如下:
function Promise(something) { var callbacks = []; this.then = function (onCompelete) { return new Promise(function (resolve) { callbacks.push({ onCompelete: onCompelete, resolve: resolve }); }); }; function resolve(value) { if (value && (typeof value.then === "function")) { value.then.call(value, resolve); return; } setTimeout(function(){ callbacks.forEach(function (cb) { var ret = cb.onCompelete(value); cb.resolve(ret); }); },0); } something(resolve); }
你會發(fā)現(xiàn),這里只是在resolve方法里面,將執(zhí)行的回調(diào)放入setTimeout中,并且timeout設(shè)為0。這里稍微說下原理
在第一篇中提到setTimeout類似定時器,JS內(nèi)容在執(zhí)行setTimeout的回調(diào)函數(shù)的時候使用線程調(diào)度的方式將回調(diào)函數(shù)調(diào)度到JS線程執(zhí)行。但凡涉及到線程調(diào)度那么肯定需要等待JS線程空閑的時候才能調(diào)度過來。這時候?qū)imeout設(shè)為0,相當于改變了代碼執(zhí)行順序。
在實際的開發(fā)過程中,上面的Promise代碼還是缺少了一個功能,那就是狀態(tài)管理,比如:pending、fulfilled、rejected。下面的代碼繼續(xù)加入狀態(tài)管理的代碼,先添加pending和fulfilled的狀態(tài):
function Promise(something) { var callbacks = []; var state = 0;//0:pending,1:fulfilled var resultValue = null; this.then = function (onCompelete) { return new Promise(function (resolve) { handleCallBack({ onCompelete: onCompelete, resolve: resolve }); }); }; function handleCallBack(callback){ switch(state){ case 0:{ callbacks.push(callback); break; } case 1:{ var ret = callback.onCompelete(resultValue); callback.resolve(ret); break; } default:{ break; } } } function resolve(value) { if (value && (typeof value.then === "function")) { value.then.call(value, resolve); return; } state = 1; resultValue = value; setTimeout(function(){ callbacks.forEach(function (cb) { handleCallBack(cb); }); },0); } something(resolve); }
下面再繼續(xù)加入reject功能。
function Promise(something) { var callbacks = []; var state = 0;//0:pending,1:fulfilled 2:reject var resultValue = null; this.then = function (onCompelete, onReject) { return new Promise(function (resolve) { handleCallBack({ onCompelete: onCompelete, resolve: resolve, reject: onReject }); }); }; function handleCallBack(callback) { switch (state) { case 0: { callbacks.push(callback); break; } case 1: { var ret = callback.onCompelete(resultValue); callback.resolve(ret); break; } case 2: { if(callback.reject){ var ret = callback.reject(resultValue); } callback.resolve(ret); break; } default: { break; } } } function reject(error) { state = 2; resultValue = error; setTimeout(function () { callbacks.forEach(function (cb) { handleCallBack(cb); }); }, 0); } function resolve(value) { if (value && (typeof value.then === "function")) { value.then.call(value, resolve); return; } state = 1; resultValue = value; setTimeout(function () { callbacks.forEach(function (cb) { handleCallBack(cb); }); }, 0); } something(resolve,reject); }
OK,通過上面一步一步對Promise進行修改,基本上是把Promise的功能完善了。
從這個上面一步一步剖析Promise原理的過程中,我們發(fā)現(xiàn),Promise本身并不提供異步功能,Promise只是對函數(shù)的回調(diào)功能進行了封裝,甚至可以理解為Promise就是一個回調(diào)代理。但是正是有了這個回調(diào)代理,使得我們的回調(diào)方式發(fā)生了徹底的改變,甚至直接影響了項目的架構(gòu)設(shè)計。而在平時的開發(fā)過程中,Promise在異步編程中起到了幾乎不可替代的作用。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/106711.html
摘要:的翻譯文檔由的維護很多人說,阮老師已經(jīng)有一本關(guān)于的書了入門,覺得看看這本書就足夠了。前端的異步解決方案之和異步編程模式在前端開發(fā)過程中,顯得越來越重要。為了讓編程更美好,我們就需要引入來降低異步編程的復雜性。 JavaScript Promise 迷你書(中文版) 超詳細介紹promise的gitbook,看完再不會promise...... 本書的目的是以目前還在制定中的ECMASc...
摘要:從最開始的到封裝后的都在試圖解決異步編程過程中的問題。為了讓編程更美好,我們就需要引入來降低異步編程的復雜性。寫一個符合規(guī)范并可配合使用的寫一個符合規(guī)范并可配合使用的理解的工作原理采用回調(diào)函數(shù)來處理異步編程。 JavaScript怎么使用循環(huán)代替(異步)遞歸 問題描述 在開發(fā)過程中,遇到一個需求:在系統(tǒng)初始化時通過http獲取一個第三方服務(wù)器端的列表,第三方服務(wù)器提供了一個接口,可通過...
摘要:函數(shù)會在之后的某個時刻觸發(fā)事件定時器。事件循環(huán)中的這樣一次遍歷被稱為一個。執(zhí)行完畢并出棧。當定時器過期,宿主環(huán)境會把回調(diào)函數(shù)添加至事件循環(huán)隊列中,然后,在未來的某個取出并執(zhí)行該事件。 原文請查閱這里,略有改動。 本系列持續(xù)更新中,Github 地址請查閱這里。 這是 JavaScript 工作原理的第四章。 現(xiàn)在,我們將會通過回顧單線程環(huán)境下編程的弊端及如何克服這些困難以創(chuàng)建令人驚嘆...
摘要:從最開始的到封裝后的都在試圖解決異步編程過程中的問題。為了讓編程更美好,我們就需要引入來降低異步編程的復雜性。異步編程入門的全稱是前端經(jīng)典面試題從輸入到頁面加載發(fā)生了什么這是一篇開發(fā)的科普類文章,涉及到優(yōu)化等多個方面。 TypeScript 入門教程 從 JavaScript 程序員的角度總結(jié)思考,循序漸進的理解 TypeScript。 網(wǎng)絡(luò)基礎(chǔ)知識之 HTTP 協(xié)議 詳細介紹 HTT...
閱讀 2591·2021-11-18 10:02
閱讀 2627·2021-11-15 11:38
閱讀 3699·2021-11-12 10:36
閱讀 696·2021-11-12 10:34
閱讀 2888·2021-10-21 09:38
閱讀 1479·2021-09-29 09:48
閱讀 1492·2021-09-29 09:34
閱讀 1088·2021-09-22 10:02