有關(guān)Promises異步問題詳解
迄今為止,可能每個JavaScript開發(fā)者和他們的祖母都聽說過Promises。如果你沒有,那么你即將會。promises的概念是由CommonJS小組的成員在 Promises/A規(guī)范 中提出來的。Promises被逐漸用作一種管理異步操作回調(diào)的方法,但出于它們的設(shè)計,它們遠比那個有用得多。事實上,由于它們的多種用法,有無數(shù)人告訴我——在我寫過一些關(guān)于promises的東西后——我“遺漏了promises的重點”。那么什么是promises的重點呢?
一點關(guān)于Promises的東西
在我開始promise的“重點”之前,我想我應(yīng)該給你一點它們?nèi)绾喂ぷ鞯膬?nèi)貌。一個promise是一個對象——根據(jù)Promise/A規(guī)范——只需要一個方法:then。then方法帶有三個參數(shù):一個成功回調(diào),一個失敗回調(diào),和一個前進回調(diào)(規(guī)范沒有要求包括前進回調(diào)的實現(xiàn),但是很多都實現(xiàn)了)。一個全新的promise對象從每個then的調(diào)用中返回。
一個promise可以是三種狀態(tài)之一:未完成的,完成的,或者失敗的。promise以未完成的狀態(tài)開始,如果成功它將會是完成態(tài),如果失敗將會是失敗態(tài)。當(dāng)一個promise移動到完成態(tài),所有注冊到它的成功回調(diào)將被調(diào)用,而且會將成功的結(jié)果值傳給它。另外,任何注冊到promise的成功回調(diào),將會在它已經(jīng)完成以后立即被調(diào)用。
同樣的事情發(fā)生在promise移動到失敗態(tài)的時候,除了它調(diào)用的是失敗回調(diào)而不是成功回調(diào)。對包含前進特性的實現(xiàn)來說,promise在它離開未完成狀態(tài)以前的任何時刻,都可以更新它的progress。當(dāng)progress被更新,所有的前進回調(diào)(progress callbacks)會被傳遞以progress的值,并被立即調(diào)用。前進回調(diào)被以不同于成功和失敗回調(diào)的方式處理;如果你在一個progress更新已經(jīng)發(fā)生以后注冊了一個前進回調(diào),新的前進回調(diào)只會在它被注冊以后被已更新的progress調(diào)用。
我們不會進一步深入promise狀態(tài)是如何管理的,因為那不在規(guī)范之內(nèi),而且每個實現(xiàn)都有差別。在后面的例子中,你將會看到它是如何完成的,但目前這就是所有你需要知道的。
處理回調(diào)
像前面提到的為異步操作處理回調(diào),是promises的最基本和最普通的用途,讓我們將一個標(biāo)準(zhǔn)的回調(diào)與一個采用了promise的回調(diào)比較一下。
// Normal callback usage asyncOperation(function() { // Here's your callback }); // Now `asyncOperation` returns a promise asyncOperation().then(function(){ // Here's your callback });
我很懷疑只是看到這個例子的話是否有人會真的關(guān)心去使用promises??雌饋頉]有什么好處,除了“then”使得在異步操作完成之后的回調(diào)函數(shù)被調(diào)用這件事看起來更加明顯。但是即使是這個好處,我們現(xiàn)在有了更多的代碼(抽象應(yīng)該使我們的代碼更短,不是嗎?)而且promise比標(biāo)準(zhǔn)回調(diào)稍微性能差一點。
但是,不要讓這阻礙到你。如果這就是promise可以做的最好的事,這篇文章就不會存在了
厄運的金字塔
// Normal callback usage => PYRAMID OF DOOOOOOOOM asyncOperation(function(data){ // Do some processing with `data` anotherAsync(function(data2){ // Some more processing with `data2` yetAnotherAsync(function(){ // Yay we're finished! }); }); }); // Let's look at using promises asyncOperation() .then(function(data){ // Do some processing with `data` return anotherAsync(); }) .then(function(data2){ // Some more processing with `data2` return yetAnotherAsync(); }) .then(function(){ // Yay we're finished! });
正如你所見,promises的使用使得事情變扁平而且更可讀了。這能起作用是因為——像早先提到的——then返回了一個promise,所以你可以將then的調(diào)用不停的串連起來。由then返回的promise裝載了由調(diào)用返回的值。如果調(diào)用返回了一個promise(像這個例子中的情形一樣),then返回的 promise裝載了與你的回調(diào)返回的promise所裝載的相同值。這內(nèi)容很多,因此我將幫助你一步一步的理解它
異步操作返回一個promise對象。因此我們在那個promise對象中調(diào)用then,并且傳給它一個回調(diào)函數(shù);then也會返回一個promise。當(dāng)異步操作結(jié)束,它將給promise裝上數(shù)據(jù)。然后(第一次)回調(diào)被調(diào)用,數(shù)據(jù)被作為參數(shù)傳遞進去。如果回調(diào)不含有返回值,then返回的promise將會立即不帶值組裝。如果回調(diào)返回的不是一個promise,那么then返回的 promise將會立即裝載那個數(shù)值。如果回調(diào)返回一個promise(像例子中的),那么then返回的 promise將等待直到我們回調(diào)返回的promise被完全裝載。一旦我們回調(diào)的 promise被裝載,它裝載的值(本例中就是data2)將會被提交給then的promise。然后then中的promise裝載了data2。等等。聽起來有點復(fù)雜,但事實上很簡單,如果我說的你不能理解,我非常抱歉。我猜我可能不是談?wù)撍淖罴讶诉x。
用命名的回調(diào)替代
但顯然 promises 不是使這個結(jié)構(gòu)扁平化的唯一方法。在寫了一篇提到promises解決了厄運的金字塔問題的帖子之后,有個人對該帖評論說……
我想promises有時是有用的,但是“嵌套”的回調(diào)的問題(圣誕樹綜合癥)可以僅用一個命名的函數(shù)作為一個參數(shù)替代匿名函數(shù)的方法平常的處理:
asyncCall( param1, param2, HandlerCallback ); function HandlerCallback(err, res){ // do stuff }
它的例子只是給出了一層深的例子,但它仍是正確的。我們來擴展我前面的例子,使這個看起來容易些。
命名回調(diào)
// Normal callback usage => PYRAMID OF DOOOOOOOOM asyncOperation(handler1); function handler1(data) { // Do some processing with `data` anotherAsync(handler2); } function handler2(data2) { // Some more processing with `data2` yetAnotherAsync(handler3); } function handler3() { // Yay we're finished! }
看看上面的代碼!他們絕對是對的!它就是一個扁平的結(jié)構(gòu),但是這里有個問題同樣也存在于 我以前從來沒有注意過的老的回調(diào)例子中:依賴性和復(fù)用性。依賴性和復(fù)用性是相互關(guān)聯(lián)的可逆類型。一樣?xùn)|西依賴的越少,那么它的復(fù)用性就越大。在以上的例子中,handler1依賴handler2,handler2依賴handler3.這就意味著handler1無論出于任何目的都不可在被用除非handler2也呈現(xiàn)出來。假如你不打算重用他們,那么給你的函數(shù)命名又有什么意義呢?
最糟糕的的是handler1都不關(guān)心在handler2里面發(fā)生了什么事情。它壓根就不需要handler2除了和它異步工作。因此,讓我們消除這些依賴性然后通過用promise使函數(shù)更具復(fù)用性。
鏈?zhǔn)交卣{(diào)
asyncOperation().then(handler1).then(handler2).then(handler3); function handler1(data) { // Do some processing with `data` return anotherAsync(); } function handler2(data2) { // Some more processing with `data2` return yetAnotherAsync(); } function handler3() { // Yay we're finished! }
這樣看起來是不是好多了?假如另外的函數(shù)存在的話,現(xiàn)在handler1和handler2都互不相關(guān)了。想看看他們是否真的很棒呢?現(xiàn)在handler1可以被用在不需要handler2的情況下了。相反,handler1被操作以后,我們將可以用另一個handler。
復(fù)用函數(shù)
asyncOperation().then(handler1).then(anotherHandler); function handler1(data) { // Do some processing with `data` return anotherAsync(); } function anotherHandler(data2) { // Do some really awesome stuff that you've never seen before. It'll impress you }
現(xiàn)在handler1已經(jīng)從handler2脫離而且可以被用在了更多的情形中,特別是那些由handler2提供的功能而我們又不想用的。這就是復(fù)用性!評論家解決代碼易讀性的唯一方法就是通過消除縮進。我們不想消除縮進僅僅是為了縮進。多層次的縮進僅僅是某些事情錯誤的標(biāo)志,問題不一定在它本身。他就像是由脫水引起的頭痛。真正的問題是脫水,不是頭痛。解決的方法是獲得水合物,而不是用一些止痛藥。
并行異步操作
在前面我提到的文章里,我將promises與events在處理異步操作方面做了比較。遺憾的是,按照那些曾提到過的人在評論里給的說法,我比較的不是很成功。我描述出了promises的力量,接著轉(zhuǎn)到events來描述它們的力量,就像在我的特別項目里用到的那樣。沒有比較和對比。一位評論者寫道(修改了一點語法錯誤):
我想用帖子中的例子是一個壞的對照。有篇論文證明了promises的值將會怎樣,如果按下虛構(gòu)的“啟動服務(wù)器按鈕”,將不僅僅是啟動一個web服務(wù)器,還有一個數(shù)據(jù)庫服務(wù)器,當(dāng)它們都在運行的時候只是更新了UI。
使用promise的.when方法將會使這種“多個異步操作”例子變得普通,然而響應(yīng)多個異步事件需要一個并不普通的代碼量。
他完全正確。事實上我沒有比較那兩種情況。那篇文章的要點實際在于說明promises不是異步操作的唯一機制,而且在一些情況下,它們也不一定是最好的。在這個評論者指出的情況下,promises當(dāng)然是最佳的解決辦法。我們來看看他說的是什么
jQuery 具有 一個名為when的方法 ,可以帶上任意數(shù)量的promise參數(shù),并返回一個單一的promise。如果任何一個promise傳入失敗,when返回的promise也會失敗。如果所有的promises被裝載,那么每個值都將會按照promises被定義的順序傳遞給附加的回調(diào)。
以并行的方式執(zhí)行無數(shù)的異步操作非常有用,然后只要在它們之中的每一個結(jié)束之后繼續(xù)執(zhí)行回調(diào)。我們看一個簡單的例子。
jQuery.when
// Each of these async functions return a promise var promise1 = asyncOperation1(); var promise2 = asyncOperation2(); var promise3 = asyncOperation3(); // The $ refers to jQuery $.when(promise1, promise2, promise3).then( function(value1, value2, value3){ // Do something with each of the returned values } );
人們經(jīng)常說這是 promises 帶來的最好的東西之一,也是 promises 的一部分重要的意義所在。我也認(rèn)為這是個簡化了大量操作的好特性,但是這種 when 方法的機制 根本就沒有在任何 Promises 規(guī)范中提到,所以我不認(rèn)為它是 Promises意義所在。有一個規(guī)范提到了 when 方法,但是和上面的完全不同。就我所知,jQuery 是唯一的實現(xiàn)了這種 when 方法的庫。其他的 promises 庫,例如 Q, Dojo, 和 when 依照 Promises/B spec 實現(xiàn)了 when 方法, 但是并沒有實現(xiàn)注釋者提及的 when 方法。但是,Q 庫有一個 all方法,when.js 也有一個 parallel方法,與上面的 jQuery.when 方法作用一樣,只是它們接受一個數(shù)組類型的參數(shù),而不是任意數(shù)量的參數(shù)。
值的表示
Promise是處理以下場景的更好的方法:
"我想在這個數(shù)據(jù)庫中找一個用戶,但find方法是異步的。"
因此,這里我們有了一個不能立刻返回值的find方法。但最終它確實"返回"了一個數(shù)值(通過一個回調(diào)的方式),而你希望以某種方式處理那個數(shù)值?,F(xiàn)在,通過使用一個回調(diào),你能定義一個繼續(xù)部分,或者說“一些將在以后時間里處理那個數(shù)值的代碼”
Promise改變了那種“嘿,這里是一些你會發(fā)現(xiàn)你用來處理返回數(shù)值的代碼”。它們是一些允許"find"方法說“嘿,我將忙著找你要找的信息,但與此同時你能繼續(xù)等著返回結(jié)果,而且你能同時以任何你希望的方式處理它,就像實際的東西!”
Promise代表了真實的數(shù)值。那就是陷阱。它們工作在你像處理實際東西一樣處理Promise的時候。Promise的JavaScript實現(xiàn)期待你給它傳遞一個回調(diào)函數(shù),這只是一個“巧合”,它不是重要的事情。
我相信這真的就是promise的重點。為什么?讀一讀 Promise/A規(guī)范 的第一句“一個promise代表了一個操作的一次完成最終返回的數(shù)值?!笆顾悬c明顯了,是不是?好吧,即使那就是重點,那也不能阻止我在后面本文中呈現(xiàn)其他人的見解。不管怎么說,我們再多談?wù)撨@個思想一點。
結(jié)論
promise的重點是它代表一個操作返回的最終結(jié)果值,但使用它們的原因是使同步操作更好的并行。自從異步編程進入此場景,到處都是彈出的回調(diào),以奇怪的方式遮住我們的代碼。Promise是一種改變其的方法。Promise允許我們以同步的方式寫代碼,同時給予我們代碼的異步執(zhí)行。
- javascript使用Promise對象實現(xiàn)異步編程
- 簡單實現(xiàn)異步編程promise模式
- 異步JavaScript編程中的Promise使用方法
- NodeJS中利用Promise來封裝異步函數(shù)
- javascript異步編程代碼書寫規(guī)范Promise學(xué)習(xí)筆記
- JavaScript異步回調(diào)的Promise模式封裝實例
- Javascript中的異步編程規(guī)范Promises/A詳細介紹
- Javascript異步編程模型Promise模式詳細介紹
- JavaScript異步編程Promise模式的6個特性
- 使用Promise解決多層異步調(diào)用的簡單學(xué)習(xí)心得
相關(guān)文章
解讀TypeScript與JavaScript的區(qū)別
這篇文章主要介紹了TypeScript與JavaScript的區(qū)別及說明,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-12-12微信小程序?qū)崿F(xiàn)多行文字超出部分省略號顯示功能
這篇文章主要介紹了微信小程序?qū)崿F(xiàn)多行文字 超出部分省略號顯示功能,比如設(shè)置只顯示2行,超出部分省略號顯示,本文通過實例代碼給大家介紹,需要的朋友可以參考下2019-10-10JS實現(xiàn)網(wǎng)頁搶購功能(觸發(fā),終止腳本)
小編通過一個網(wǎng)頁式的搶購功能的實現(xiàn)給大家講解一下JS如何觸發(fā)和終止腳本來完成這個任務(wù)。2017-11-11JavaScript關(guān)閉當(dāng)前頁面(窗口)不帶任何提示
這篇文章主要介紹了JavaScript關(guān)閉當(dāng)前頁面(窗口)不帶任何提示的具體實現(xiàn),需要的朋友可以參考下2014-03-03淺談監(jiān)聽單選框radio改變事件(和layui中單選按鈕改變事件)
今天小編就為大家分享一篇淺談監(jiān)聽單選框radio改變事件(和layui中單選按鈕改變事件),具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-09-09js模仿微信朋友圈計算時間顯示幾天/幾小時/幾分鐘/幾秒之前
本篇文章主要介紹了js模仿微信朋友圈計算時間顯示幾天/幾小時/幾分鐘/幾秒之前的實例。具有很好的參考價值。下面跟著小編一起來看下吧2017-04-04js實現(xiàn)window.open不被攔截的解決方法匯總
這篇文章主要介紹了js實現(xiàn)window.open不被攔截的解決方法,實例匯總了常用的不被攔截的解決方法,需要的朋友可以參考下2014-10-10鼠標(biāo)選擇動態(tài)改變網(wǎng)頁背景顏色的JS代碼
這篇文章主要介紹了鼠標(biāo)選擇動態(tài)改變網(wǎng)頁背景顏色的JS代碼,有需要的朋友可以參考一下2013-12-12