摘要:這下不管是同步還是異步,我們隨時可以在方法中去取值,如果值沒有被,也就是說狀態(tài)沒發(fā)生變化,將給我們記錄下這件事,等到的那個時間點把值傳給方法中那個回調(diào)函數(shù),。
一本正經(jīng)的扯淡
顧名思義,promise中文意思就是承諾,也就是現(xiàn)在實現(xiàn)不了將來?????,但是將來這玩意誰說的準呢。就像你去泡妞,你可能許下各種諾言,但能不能實現(xiàn),完全取決于你這人靠不靠譜。好在計算機不是人,不是人,不是人,????正因為不是人,所以它許下的承諾,它就一定會給你一個結(jié)果。
等待承諾實現(xiàn)的過程中很漫長,所以你可以做一些其它的事情,沒必要老是堵在這一條道上,也就是異步。打個比方,你打電話給飯店老板叫了個外賣,老板告訴你,10分鐘后送過去,也就是說老板給了你一個承諾,于是你等啊等,這中間又去上了個廁所,玩了會手機??????,這就是異步,老板給的承諾并沒有妨礙你干其它的事情。OK,扯淡結(jié)束。
為了實現(xiàn)異步,一般要設(shè)置一個回調(diào)函數(shù)
setTimeout(function(){ console.log(1); setTimeout(function(){ console.log(2); setTimeout(function(){ console.log(3); setTimeout(function(){ console.log(4); setTimeout(function(){ console.log(5); },500) },400) },300) },200) },100);
??????有沒有一種想死的感覺!
promise最大優(yōu)勢就是第一消滅了這種回調(diào)地獄,第二增加了錯誤捕獲,像下面這種,
promis.then(function (response) { //do something; }, function (reason) { //get error }).then(function (response) { //do something; }, function (reason) { //get error }).then(function (response) { //do something; }, function (reason) { //get error });
如果不做錯誤處理則更清晰
promis.then(function (response) { //do something; }).then(function (response) { //do something; }).then(function (response) { //do something; }).then(function (response) { //do something; });
它使得異步的代碼看起來像是在同步執(zhí)行,大大增強了代碼的可讀性。
美中不足的是你得寫一堆的.then(function(){},function(){}),但是和回調(diào)地獄相比,忍了。
在ES7中會有號稱是異步的終極解決方案,async和await,那是后話。
前面說了,計算機不是人,所以它許下的承諾,它一定會給你一個結(jié)果,不管這個承諾的結(jié)果是接受還是拒絕。
所以,第一,promise一定會返回一個結(jié)果。
第二,這個結(jié)果是不可逆的,你只能接受,本質(zhì)是因為promise的狀態(tài)不可逆,一旦它變成了resolve或者reject,你休想再讓你變成pending,否則,它要會說話,肯定回你的只有一個字,滾!
第三、promise的結(jié)果什么時候返回,你說了不算,你去泡妞的時候,妞也不一定當場就答應(yīng)你吧,或許想個三、五天也說不定,這個主動權(quán)不是掌握在你手中的。
第四、ES6的promise執(zhí)行過程中,你是無法獲得執(zhí)行的進度的,到底它現(xiàn)在是pending還是resolve,還是reject。就好像妞和她的閨蜜探討要不要接受你,你是打聽不到的。當然并不是完全不能,例如angularjs的$q實現(xiàn)一個notify方法,可以獲取到執(zhí)行進度的通知。
最后說一點兒你的權(quán)力,你能決定的是在什么時候去取promise的結(jié)果,也就是調(diào)用then方法的時間,就好像你每天追著妞問,你想好沒有??????,妞這個時候會有三種回答,一是答應(yīng)你,二是拒絕你,三是還得再想想,XXXX時候再告訴你????,也就說這TMD又是一個承諾?????。
咳、咳,現(xiàn)在開始必須嚴肅點,畢竟技術(shù)是一件嚴肅的事情。
說白了,promise就是一個對象,一個只能通過then方法來取得它上面的值的對象。
在es6中,你只要大喊一句,妞,給我個承諾,它就會給你一個promise,就像下面這樣:
var promise = new Promise(function(resolve,reject){ //do something; })
然后你就可以調(diào)用它的then方法去取值,那么從這個角度看,這個構(gòu)造函數(shù)一定是返回了一個帶有then方法的對象。另外還有狀態(tài),狀態(tài)的變化不可逆。再加上些其它的方法,如all、catch???,不過不要著急,我們一步一步來意淫出這個漂亮的妞????
1、通常情況,我們使用回調(diào)一個函數(shù)內(nèi)執(zhí)行另外一個函數(shù):
function doSomething(callback){ console.log("do something"); callback(); } doSomething(function(){ console.log("a"); });
2、但是在使用promise時,我們是用then方法去取結(jié)果,而promise就是個對象,那么上面的代碼看起來應(yīng)該這樣寫:
function doSomething(){ console.log("a"); return { then: function(callback){ var value = 1; callback(value); } } } doSomething().then(function(res){ console.log(res); });
在這里,我們調(diào)用dosomething函數(shù)時,返回了一個帶有then方法的對象,然后在then方法回調(diào)中去執(zhí)行,現(xiàn)在看來是不是有那么點樣子了,時刻記得兩件事,對象, then方法。
3、在ES6中Promise是一個構(gòu)造函數(shù),這簡單,給這個dosomething換個名字,
function Promise(){ this.then = function(callback){ var value = 1; callback(value); } }
在實例化promise的時候,要傳一個函數(shù)進去,這也簡單
function Promise(fn){ this.then = function(callback){ callback(); } }
實例化傳入的函數(shù)fn中(下文中的fn都是指代這個匿名函數(shù)),你會傳入2個參數(shù),一個叫resolve,另一個叫reject,為了簡單起見,我們不考慮reject,它的道理和resolve是一樣的。那么就像這樣:
var promise = new Promise(function(resolve){ var value = 1; resolve(value); })
即然傳了一個fn函數(shù)進去,那么在實例化過程中,這個函數(shù)一定會在某個時刻執(zhí)行。執(zhí)行時,它又會接收到一個參數(shù)resolve,這個resolve一定是一個函數(shù),這點從上面就可以很明顯的看出來,resolve在實例化時執(zhí)行了,而且接收到了一個參數(shù),在這里是變量value。那么Promise函數(shù)內(nèi)部很可能是這樣:
function Promise(fn){ function resolve(value){} this.then = function (onResolved) {}; fn(resolve); }
為了看起來更直接,這里我們把調(diào)用then方法傳的第一個函數(shù)就叫做onResolved,那么接下來我們應(yīng)該考慮在實例化的時候,還有什么事情要做,在then方法的回調(diào)函數(shù)中我們希望得到promise的值,這個值是在fn函數(shù)調(diào)用后被resolve函數(shù)運算后得到的,最終要在onResolved函數(shù)中拿到,也就是說,我們必須在resolve中將這個值傳遞給onResolved,迂回一下:
function Promise(fn) { var callback = null; function resolve(value) { callback(value); } this.then = function(onResolved) { callback = onResolved; }; fn(resolve); }
但是這里有一個問題,就是我們調(diào)用resolve方法時,還沒有調(diào)用過then方法,因此callbak是null,瀏覽器報錯:callback is not a function,這里hack下,讓resolve方法的執(zhí)行在then之后。
function Promise(fn) { var callback = null; function resolve(value) { setTimeout(function(){ callback(value); },0) } this.then = function(onResolved) { callback = onResolved; }; fn(resolve); }
執(zhí)行一下,
var promise = new Promise(function(res){ var value = 2; res(2); }); promise.then(function(res){ console.log(res); })
OK,成功的輸出。目前為止,promise的輪廓算是被我們意淫出來了。
4、promise是有狀態(tài)的,而且狀態(tài)不可逆,同樣的為了簡單起見,我先來搞定從pending變到resolved,那么rejected也一樣。仔細想下,執(zhí)行了resolve方法后可以得到一個resolved狀態(tài)的值,那么必然在resolve方法中會去改變promise的狀態(tài),并且得到這個值,那么代碼貌似應(yīng)該這樣寫:
function Promise(fn) { var state = "pending"; function resolve(newValue) { state = "resolved"; callback(newValue); } this.then = function(onResolved) { callback = onResolved; }; fn(resolve); }
這里我們先把setTimeout這家伙給干掉了,因為我們加入了狀態(tài),也就意味我們是想通過狀態(tài)的變化來知道能不能得到值,那么問題來了,我們不知道狀態(tài)啥時候變,就像你不知道你要泡的妞啥時候答應(yīng)你一樣,你只能追問,萬一妞沒想好,她很可能再給你一個承諾,就是那個該死的XXX時候再告訴你,不過好歹她也算給了你一個等待的機會,而我們現(xiàn)在要做的就是創(chuàng)造這么個機會。
function Promise(fn) { var state = "pending"; var value; var deferred; function resolve(newValue) { value = newValue; state = "resolved"; if(deferred) { handle(deferred); } } function handle(onResolved) { if(state === "pending") { deferred = onResolved; return; } onResolved(value); } this.then = function(onResolved) { handle(onResolved); }; fn(resolve); }
這里引入了另外一個函數(shù)handle,至此可以說promise的最關(guān)鍵的東西我們已經(jīng)看到了,妞的身材逐漸顯現(xiàn)。又扯遠了?????
仔細看下除了handle我們還引入兩個變量value和deferred,先從最簡單的來:
value的作用很簡單,在構(gòu)造函數(shù)內(nèi)它是一個全局變量,起到一個橋梁作用,就是為了在handle函數(shù)內(nèi)能取到newValue的值,而newValue就是fn函數(shù)里的那個結(jié)果。
handle我們估且可以認為它是妞的一個管家,它會去替我們詢問妞有沒有想好,也就是去判斷當前這個承諾的狀態(tài),再決定怎么做。
deferred我們估且可以這樣理解,它就是管家的一個記事本,你隔三差五的去問,它老人家不得記下來,如果一不小心忘了,那就悲催了。
這下不管是同步還是異步,我們隨時可以在then方法中去取值,如果值沒有被resolve,也就是說狀態(tài)沒發(fā)生變化,deferred將給我們記錄下這件事,等到resolve的那個時間點把值傳給then方法中那個回調(diào)函數(shù),onResolved。
在這里請默念一百遍handle,defer,再接著往下看,我保證他們會讓你困惑。
5、回到最初,為什么要用promise,想想回調(diào)地獄,再想想promise是怎么解決的,那就是then方法鏈式調(diào)用。
能夠?qū)崿F(xiàn)鏈式調(diào)用,也就是說then方法返回的值也一定是個promise,這樣你才能.then,.then的一直寫下去。廢話不說,沒代碼說個毛:
function Promise(fn) { var state = "pending"; var value; var deferred = null; function resolve(newValue) { value = newValue; state = "resolved"; if(deferred) { handle(deferred); } } function handle(handler) { if(state === "pending") { deferred = handler; return; } if(!handler.onResolved) { handler.resolve(value); return; } var ret = handler.onResolved(value); handler.resolve(ret); } this.then = function(onResolved) { return new Promise(function(resolve) { handle({ onResolved: onResolved, resolve: resolve }); }); }; fn(resolve); }
這下?lián)Q個姿勢,我們先啃硬貨。我們讓then方法返回了一個promise,而且這個promise實例化時傳入的函數(shù)里調(diào)用了handle函數(shù),傳入了一個對象,onResolved很顯然就是then方法里第一個函數(shù),沒什么可說的。關(guān)鍵是這handle和resolve是哪個?思考1分鐘。
這里我們用setTimeout簡單模擬一個異步,拿一個then看下,發(fā)生了什么:
var promise = new Promise(function(resolve){ setTimeout(function(){ resolve(1); },3000) }); promise.then(function(res){ console.log(res); })
首先我們?nèi)ew一個promise,在實例化的過程中,調(diào)用了傳進的那個函數(shù),3秒后才能執(zhí)行到resolve,緊接著調(diào)用了它的then方法,這個時候由于promise的狀態(tài)沒變,肯定取不到值,好在then方法會返回個promise,于是又執(zhí)行了一次promise的實例化過程。這里無法回避的就是作用域的問題,這個關(guān)系到handle函數(shù)執(zhí)行在哪個環(huán)境中,參數(shù)的到底從哪個地方獲取到,另外就是強大的閉包。相關(guān)知識不解釋。
為了看的更清楚,我們加入一些標記,到chrome的控制臺中調(diào)試下:
var count = 0; function Promise(fn) { var state = "pending"; var value; var deferred = null; var scope = ++count; function resolve(newValue) { value = newValue; state = "resolved"; console.log("resolve: I am in " +scope); if(deferred) { handle(deferred); } } function handle(handler) { console.log("handle: I am in " +scope); if(state === "pending") { deferred = handler; return; } if(!handler.onResolved) { handler.resolve(value); return; } var ret = handler.onResolved(value); handler.resolve(ret); } this.then = function(onResolved) { console.log("then: I am in " + scope); return new Promise(function(resolve) { console.log("then promise: I am in " + scope); handle({ onResolved: onResolved, resolve: resolve }); }); }; fn(resolve); } var promise = new Promise(function(resolve){ setTimeout(function(){ resolve(1); },3000) }); promise.then(function(res){ console.log(res); });
加入的scope是為了監(jiān)視作用域的變化,以間接反應(yīng)出我們調(diào)用handle時是在哪個作用域上查詢到的,此外我們還需要監(jiān)視state和deferred的變化。
主要看then調(diào)用之后,廢話不說上圖:
5-1、在執(zhí)行then方法的時候,scope=1,state,deferred不可用。由于模擬了異步,這個時候第一個promise的resolve方法并沒有執(zhí)行,這里模擬了3秒,實際情況下,比如ajax取數(shù)據(jù)時,我們并不知道這個準確的時間,就像開始時說的,這妞啥時候答應(yīng)你,主動權(quán)不在你手中,由妞說了算。
5-2、接下來去實例化then方法創(chuàng)建的這個promise,scope = 2,state=”pending”,deferred=null。
5-3、在實例化完成之后,此時去執(zhí)行fn函數(shù),scope=1,state,deferred不可用。
第一,函數(shù)的作用域是在定義時就生成的,而不是在調(diào)用的時候。第二個promise定義的時候,是在第一個promise作用域上,這樣即使它被return了出去,由于閉包的特性,仍讀取的是第一個作用域上值,所以這里的handle必定是第一個promise的handle。而resolve則不同,它是作為行參傳遞了進來,所以這里的resolve是第二個promise的resolve。
5-4、進入handle時,scope = 1,state =” pending”,deferred保存了參數(shù)。
5-5、3秒時間到,第一個promise里的resolve被執(zhí)行了,也就是說拿到了結(jié)果,這時候,scope=1,state = “resolved”,deferred保存著剛才傳進來的那個對象,再次進入handle函數(shù)。
5-6、scope=1,state = “resolved”,deferred求值為true,因此肯定會繼續(xù)執(zhí)行。下面添加的這段代碼在這里也就很清楚了,假如then方法中沒有傳進來的onResolved函數(shù),這里的value將直接交給下一個then方法中的onResolved函數(shù)使用,避免一些無聊的人像這樣去調(diào)用:
promise.then().then().then(function(res){ console.log(res); })
正常人都會讓value在onResolved函數(shù)中接收到,然后ret就是onResolved函數(shù)的返回值,這里沒有return回的值,所以ret肯定是undefined。
5-7、scope=2,state = “resolved”,deferred=null。這里的resolve是第個promise的resolve,所以定義的時候就是在作用域2上,如果后面再調(diào)用then方法,生成新的promise,這時就會將undefined作為第二個promise的值傳遞下去。
這里再次強調(diào)一下,handle方法和deferred是核心所在,其背后的精髓無非還是作用域和閉包的巧妙設(shè)計。變量的讀取必定先從自身所處作用域開始,如果自身作用域上讀不到,才會一級一級向上訪問。
6、意淫到這里基本上核心的東西就差不多了,下面我們來加上reject時的情況,直接上代碼:
function Promise(fn) { var state = "pending"; var value; var deferred; this.then = function (onResolved, onRejected) { return new Promise(function (resolve, reject) { handle({ onResolved: onResolved, onRejected: onRejected, resolve: resolve, reject: reject }); }); }; function resolve(newValue) { if (newValue && typeof newValue.then === "function") { newValue.then(resolve); return; } state = "resolved"; value = newValue; if (deferred) { handle(deferred); } } function reject(reason) { state = "rejected"; value = reason; if (deferred) { handle(deferred); } } function handle(handler) { if (state === "pending") { deferred = handler; return; } var handlerCallback; if (state === "resolved") { handlerCallback = handler.onResolved; } else { handlerCallback = handler.onRejected; } if (!handlerCallback) { if (state === "resolved") { handler.resolve(value); } else { handler.reject(value); } return; } var ret; try { ret = handlerCallback(value); } catch (e) { handler.reject(e); return; } handler.resolve(ret); } fn(resolve); }
情況基本和resolve是一樣的,resolve函數(shù)中加的if判斷只為了對付返回值是promise的情況下仍然可以通過后續(xù)的then方法取到值,handle中的try/catch塊的加入使得可以捕獲到promise及then方法回調(diào)中的錯誤,至于then方法的改變,看不懂的話自宮吧,你是女人當我沒說。
四、其它當然這個promise只是一個基本的實現(xiàn),依然很脆弱,但基本上可以說有了一輪廓,剩下的部位各位看官自己添加,比如promise的all ,race,catch等。某種意義上說,它們也只是then方法的語法糖。
http://www.mattgreer.org/arti...,本文代碼出處,算是學(xué)習(xí)后的一點體會,水平有限,理解不對之處,還請指正。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/80872.html
摘要:從最開始的到封裝后的都在試圖解決異步編程過程中的問題。為了讓編程更美好,我們就需要引入來降低異步編程的復(fù)雜性。寫一個符合規(guī)范并可配合使用的寫一個符合規(guī)范并可配合使用的理解的工作原理采用回調(diào)函數(shù)來處理異步編程。 JavaScript怎么使用循環(huán)代替(異步)遞歸 問題描述 在開發(fā)過程中,遇到一個需求:在系統(tǒng)初始化時通過http獲取一個第三方服務(wù)器端的列表,第三方服務(wù)器提供了一個接口,可通過...
摘要:執(zhí)行的時候呢即可如何處理鏈式的且保證順序每個后面鏈一個對象該對象包含子三個屬性當父狀態(tài)改變完畢執(zhí)行完相應(yīng)的的時候呢,拿到子在等待這個子狀態(tài)改變,在執(zhí)行相應(yīng)的。 promise源碼分析 初級入門以及如何使用請看 阮一峰promise對象講解 先上一坨代碼,后面我們要基于這坨代碼來實現(xiàn)自定義promise 原始方法 setTimeout(function(){ var a=100...
摘要:的翻譯文檔由的維護很多人說,阮老師已經(jīng)有一本關(guān)于的書了入門,覺得看看這本書就足夠了。前端的異步解決方案之和異步編程模式在前端開發(fā)過程中,顯得越來越重要。為了讓編程更美好,我們就需要引入來降低異步編程的復(fù)雜性。 JavaScript Promise 迷你書(中文版) 超詳細介紹promise的gitbook,看完再不會promise...... 本書的目的是以目前還在制定中的ECMASc...
摘要:面試題來源于網(wǎng)絡(luò),看一下高級前端的面試題,可以知道自己和高級前端的差距。 面試題來源于網(wǎng)絡(luò),看一下高級前端的面試題,可以知道自己和高級前端的差距。有些面試題會重復(fù)。 使用過的koa2中間件 koa-body原理 介紹自己寫過的中間件 有沒有涉及到Cluster 介紹pm2 master掛了的話pm2怎么處理 如何和MySQL進行通信 React聲明周期及自己的理解 如何...
摘要:今天對于處理異步調(diào)用已經(jīng)有了很多成熟的方案,在我看來這些方案都無外乎在解決一個問題如何能看似順序地傳遞異步調(diào)用的結(jié)果,本文要說的就是原生提供的一個解決方案。在對進行敘述之前,依舊引用阮大的入門一書中的章節(jié)便于大家更嚴謹和全面的學(xué)習(xí)和參考。 異步回調(diào)的泥潭 異步回調(diào)是最直接的異步結(jié)果處理模式,將一個回調(diào)函數(shù)callback扔進異步處理函數(shù)中,當異步處理獲得結(jié)果之后再調(diào)用這個回調(diào)函數(shù)就可以...
閱讀 2905·2021-11-22 13:54
閱讀 3542·2021-11-16 11:44
閱讀 1381·2021-09-07 10:19
閱讀 1483·2019-08-29 17:30
閱讀 3206·2019-08-29 11:33
閱讀 3555·2019-08-26 12:18
閱讀 2894·2019-08-26 11:53
閱讀 1347·2019-08-26 10:47