亚洲精品中文字幕无乱码_久久亚洲精品无码AV大片_最新国产免费Av网址_国产精品3级片

JavaScript

異步JavaScript編程中的Promise使用方法

時間:2024-08-25 18:28:43 JavaScript 我要投稿
  • 相關推薦

關于異步JavaScript編程中的Promise使用方法

  異步?

  我在很多地方都看到過異步(Asynchronous)這個詞,但在我還不是很理解這個概念的時候,卻發(fā)現(xiàn)自己常常會被當做“已經很清楚”(* ̄? ̄)。

  如果你也有類似的情況,沒關系,搜索一下這個詞,就可以得到大致的說明。在這里,我會對JavaScript的異步做一點額外解釋。

  看一下這段代碼:

  var start = new Date();setTimeout(function(){ var end = new Date(); console.log("Time elapsed: ", end - start, "ms");}, 500);while (new Date - start < 1000) {};

  這段代碼運行后會得到類似Time elapsed: 1013ms這樣的結果。 setTimeout()所設定的在未來500ms時執(zhí)行的函數(shù),實際等了比1000ms更多的時間后才執(zhí)行。

  要如何解釋呢?調用setTimeout()時,一個延時事件被排入隊列。然后,繼續(xù)執(zhí)行這之后的代碼,以及更后邊的代碼,直到沒有任何代碼。沒有任何代碼后,JavaScript線程進入空閑,此時JavaScript執(zhí)行引擎才去翻看隊列,在隊列中找到“應該觸發(fā)”的事件,然后調用這個事件的處理器(函數(shù))。處理器執(zhí)行完成后,又再返回到隊列,然后查看下一個事件。

  單線程的JavaScript,就是這樣通過隊列,以事件循環(huán)的形式工作的。所以,前面的代碼中,是用while將執(zhí)行引擎拖在代碼運行期間長達1000ms,而在全部代碼運行完回到隊列前,任何事件都不會觸發(fā)。這就是JavaScript的異步機制。

  JavaScript的異步難題

  JavaScript中的異步操作可能不總是簡單易行的。

  Ajax也許是我們用得最多的異步操作。以jQuery為例,發(fā)起一個Ajax請求的代碼一般是這樣的:

  // Ajax請求示意代碼$.ajax({ url: url, data: dataObject, success: function(){}, error: function(){}});

  這樣的寫法有什么問題嗎?簡單來說,不夠輕便。為什么一定要在發(fā)起請求的地方,就要把success和error這些回調給寫好呢?假如我的回調要做很多很多的事情,是要我想起一件事情就跑回這里添加代碼嗎?

  再比如,我們要完成這樣一件事:有4個供Ajax訪問的url地址,需要先Ajax訪問第1個,在第1個訪問完成后,用拿到的返回數(shù)據(jù)作為參數(shù)再訪問第2個,第2個訪問完成后再第3個...以此到4個全部訪問完成。按照這樣的寫法,似乎會變成這樣:

  $.ajax({ url: url1, success: function(data){ $.ajax({ url: url2, data: data, success: function(data){ $.ajax({ //... }); } }); }})

  你一定會覺得這種稱為Pyramid of Doom(金字塔厄運)的代碼看起來很糟糕。習慣了直接附加回調的寫法,就可能會對這種一個傳遞到下一個的異步事件感到無從入手。為這些回調函數(shù)分別命名并分離存放可以在形式上減少嵌套,使代碼清晰,但仍然不能解決問題。

  另一個常見的難點是,同時發(fā)送兩個Ajax請求,然后要在兩個請求都成功返回后再做一件接下來的事,想一想如果只按前面的方式在各自的調用位置去附加回調,這是不是好像也有點難辦?

  適于應對這些異步操作,可以讓你寫出更優(yōu)雅代碼的就是Promise。

  Promise上場

  Promise是什么呢?先繼續(xù)以前面jQuery的Ajax請求示意代碼為例,那段代碼其實可以寫成這個樣子:

  var promise = $.ajax({ url: url, data: dataObject});promise.done(function(){});promise.fail(function(){});

  這和前面的Ajax請求示意代碼是等效的?梢钥吹,Promise的加入使得代碼形式發(fā)生了變化。Ajax請求就好像變量賦值一樣,被“保存”了起來。這就是封裝,封裝將真正意義上讓異步事件變得容易起來。

  封裝是有用的

  Promise對象就像是一個封裝好的對異步事件的引用。想要在這個異步事件完成后做點事情?給它附加回調就可以了,不管附加多少個也沒問題!

  jQuery的Ajax方法會返回一個Promise對象(這是jQuery1.5重點增加的特性)。如果我有do1()、do2()兩個函數(shù)要在異步事件成功完成后執(zhí)行,只需要這樣做:

  promise.done(do1);// Other code here.promise.done(do2);

  這樣可要自由多了,我只要保存這個Promise對象,就在寫代碼的任何時候,給它附加任意數(shù)量的回調,而不用管這個異步事件是在哪里發(fā)起的。這就是Promise的優(yōu)勢。

  正式的介紹

  Promise應對異步操作是如此有用,以至于發(fā)展為了CommonJS的一個規(guī)范,叫做Promises/A。Promise代表的是某一操作結束后的返回值,它有3種狀態(tài):

  肯定(fulfilled或resolved),表明該Promise的操作成功了。 否定(rejected或failed),表明該Promise的操作失敗了。 等待(pending),還沒有得到肯定或者否定的結果,進行中。

  此外,還有1種名義上的狀態(tài)用來表示Promise的操作已經成功或失敗,也就是肯定和否定狀態(tài)的集合,叫做結束(settled)。Promise還具有以下重要的特性:

  一個Promise只能從等待狀態(tài)轉變?yōu)榭隙ɑ蚍穸顟B(tài)一次,一旦轉變?yōu)榭隙ɑ蚍穸顟B(tài),就再也不會改變狀態(tài)。 如果在一個Promise結束(成功或失敗,同前面的說明)后,添加針對成功或失敗的回調,則回調函數(shù)會立即執(zhí)行。

  想想Ajax操作,發(fā)起一個請求后,等待著,然后成功收到返回或出現(xiàn)錯誤(失敗)。這是否和Promise相當一致?

  進一步解釋Promise的特性還有一個很好的例子:jQuery的$(document).ready(onReady)。其中onReady回調函數(shù)會在DOM就緒后執(zhí)行,但有趣的是,如果在執(zhí)行到這句代碼之前,DOM就已經就緒了,那么onReady會立即執(zhí)行,沒有任何延遲(也就是說,是同步的)。

  Promise示例

  生成Promise

  Promises/A里列出了一系列實現(xiàn)了Promise的JavaScript庫,jQuery也在其中。下面是用jQuery生成Promise的代碼:

  var deferred = $.Deferred();deferred.done(function(message){console.log("Done: " + message)});deferred.resolve("morin"); // Done: morin

  jQuery自己特意定義了名為Deferred的類,它實際上就是Promise。$.Deferred()方法會返回一個新生成的Promise實例。一方面,使用deferred.done()、deferred.fail()等為它附加回調,另一方面,調用deferred.resolve()或deferred.reject()來肯定或否定這個Promise,且可以向回調傳遞任意數(shù)據(jù)。

  合并Promise

  還記得我前文說的同時發(fā)送2個Ajax請求的難題嗎?繼續(xù)以jQuery為例,Promise將可以這樣解決它:

  var promise1 = $.ajax(url1),promise2 = $.ajax(url2),promiseCombined = $.when(promise1, promise2);promiseCombined.done(onDone);

  $.when()方法可以合并多個Promise得到一個新的Promise,相當于在原多個Promise之間建立了AND(邏輯與)的關系,如果所有組成Promise都已成功,則令合并后的Promise也成功,如果有任意一個組成Promise失敗,則立即令合并后的Promise失敗。

  級聯(lián)Promise

  再繼續(xù)我前文的依次執(zhí)行一系列異步任務的問題。它將用到Promise最為重要的.then()方法(在Promises/A規(guī)范中,也是用“有then()方法的對象”來定義Promise的)。代碼如下:

  var promise = $.ajax(url1);promise = promise.then(function(data){ return $.ajax(url2, data);});promise = promise.then(function(data){ return $.ajax(url3, data);});// ...

  Promise的.then()方法的完整形式是.then(onDone, onFail, onProgress),這樣看上去,它像是一個一次性就可以把各種回調都附加上去的簡便方法(.done()、.fail()可以不用了)。沒錯,你的確可以這樣使用,這是等效的。

  但.then()方法還有它更為有用的功能。如同then這個單詞本身的意義那樣,它用來清晰地指明異步事件的前后關系:“先這個,然后(then)再那個”。這稱為Promise的級聯(lián)。

  要級聯(lián)Promise,需要注意的是,在傳遞給then()的回調函數(shù)中,一定要返回你想要的代表下一步任務的Promise(如上面代碼的$.ajax(url2, data))。這樣,前面被賦值的那個變量才會變成新的Promise。而如果then()的回調函數(shù)返回的不是Promise,則then()方法會返回最初的那個Promise。

  應該會覺得有些難理解?從代碼執(zhí)行的角度上說,上面這段帶有多個then()的代碼其實還是被JavaScript引擎運行一遍就結束。但它就像是寫好的舞臺劇的劇本一樣,讀過一遍后,JavaScript引擎就會在未來的時刻,依次安排演員按照劇本來演出,而演出都是異步的。then()方法就是讓你能寫出異步劇本的筆。

  將Promise用在基于回調函數(shù)的API

  前文反復用到的$.ajax()方法會返回一個Promise對象,這其實只是jQuery特意提供的福利。實際情況是,大多數(shù)JavaScript API,包括Node.js中的原生函數(shù),都基于回調函數(shù),而不是基于Promise。這種情況下使用Promise會需要自行做一些加工。

  這個加工其實比較簡單和直接,下面是例子:

  var deferred = $.Deferred();setTimeout(deferred.resolve, 1000);deferred.done(onDone);

  這樣,將Promise的肯定或否定的觸發(fā)器,作為API的回調傳入,就變成了Promise的處理模式了。

  Promise是怎么實現(xiàn)出來的?

  本文寫Promise寫到這里,你發(fā)現(xiàn)了全都是基于已有的實現(xiàn)了Promise的庫。那么,如果要自行構筑一個Promise的話呢?

  位列于Promises/A的庫列表第一位的Q可以算是最符合Promises/A規(guī)范且相當直觀的實現(xiàn)。如果你想了解如何做出一個Promise,可以參考Q提供的設計模式解析。

  限于篇幅,本文只介紹Promise的應用。我會在以后單獨開一篇文章來詳述Promise的實現(xiàn)細節(jié)。

  作為JavaScript后續(xù)版本的ECMAScript 6將原生提供Promise,如果你想知道它的用法,推薦閱讀JavaScript Promises: There and back again。

  結語

  Promise這個詞頑強到不適合翻譯,一眼之下都會覺得意義不明。不過,在JavaScript里做比較復雜的異步任務時,它的確可以提供相當多的幫助。

【異步JavaScript編程中的Promise使用方法】相關文章:

excel中sumif函數(shù)使用方法03-23

Excel中COUNTIF函數(shù)的使用方法01-23

如何調試javascript腳本呢07-19

javascript跨域訪問的方法07-19

數(shù)控編程代碼大全02-13

早教機使用方法10-24

PHP Socket編程過程02-09

數(shù)控機床編程步驟11-08

柔膚水的作用和使用方法09-05

淡奶油的具體使用方法07-14