非同步JavaScript程式設計中的Promise使用方法_node.js?1.1.5
非同步?
我在很多地方都看過異步(Asynchronous)這個詞,但在我還不是很理解這個概念的時候,卻發現自己常常會被當做「已經很清楚」(*  ̄? ̄)。
如果你也有類似的情況,沒關係,搜尋一下這個詞,就可以得到大致的說明。在這裡,我會對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時執行的函數,實際上等了比1000ms更多的時間後才執行。
要如何解釋呢?當呼叫setTimeout()時,一個延時事件被排入佇列。然後,繼續執行這之後的程式碼,以及更後邊的程式碼,直到沒有任何程式碼。沒有任何程式碼後,JavaScript執行緒進入空閒,此時JavaScript執行引擎才去翻看佇列,在佇列中找到「應該觸發」的事件,然後呼叫這個事件的處理器(函數)。處理器執行完成後,再返回佇列,然後查看下一個事件。
單執行緒的JavaScript,就是這樣透過佇列,以事件循環的形式工作的。所以,前面的程式碼中,是用while將執行引擎拖在程式碼運行期間長達1000ms,而在全部程式碼運行完回到佇列前,任何事件都不會觸發。這就是JavaScript的非同步機制。
JavaScript的非同步難題
JavaScript中的非同步操作可能不總是簡單易行的。
Ajax也許是我們用得最多的非同步操作。以jQuery為例,啟動一個Ajax請求的程式碼一般是這樣的:
// Ajax请求示意代码 $.ajax({ url: url, data: dataObject, success: function(){}, error: function(){} });
這樣的寫法有什麼問題嗎?簡單來說,不夠輕便。為什麼一定要在發起請求的地方,就要把success和error這些回呼給寫好呢?假如我的回調要做很多很多的事情,是要我想起一件事情就跑回這裡加碼嗎?
再比如,我們要完成這樣一件事:有4個供Ajax訪問的url地址,需要先Ajax訪問第1個,在第1個訪問完成後,用拿到的返回數據作為參數再訪問第2個,第2個訪問完成後再第3個...以此到4個全部訪問完成。依照這樣的寫法,似乎會變成這樣:
$.ajax({ url: url1, success: function(data){ $.ajax({ url: url2, data: data, success: function(data){ $.ajax({ //... }); } }); } })
你一定會覺得這種稱為Pyramid of Doom(金字塔厄運)的程式碼看起來很糟。習慣了直接附加回呼的寫法,就可能會對這種一個傳遞到下一個的非同步事件感到無從入手。為這些回調函數分別命名並分離存放可以在形式上減少嵌套,使程式碼清晰,但仍然無法解決問題。
另一個常見的困難是,同時發送兩個Ajax請求,然後要在兩個請求都成功返回後再做一件接下來的事,想一想如果只按前面的方式在各自的呼叫位置去附加回調,這是不是好像也有點難辦?
適於應付這些非同步操作,可以讓你寫出更優雅程式碼的就是Promise。
Promise上場
Promise是什麼呢?先繼續以前面jQuery的Ajax請求示意程式碼為例,那段程式碼其實可以寫成這個樣子:
var promise = $.ajax({ url: url, data: dataObject }); promise.done(function(){}); promise.fail(function(){});
這和前面的Ajax請求示意代碼是等效的。可以看到,Promise的加入使得程式碼形式改變了。 Ajax請求就好像變數賦值一樣,被「保存」了起來。這就是封裝,封裝將真正意義上讓非同步事件變得容易起來。
封裝是有用的
Promise物件就像是一個封裝好的對非同步事件的引用。想要在這個非同步事件完成後做點事情?給它附加回呼就可以了,不管附加多少個也沒問題!
jQuery的Ajax方法會傳回一個Promise物件(這是jQuery1.5重點增加的特性)。如果我有do1()、do2()兩個函數要在非同步事件成功完成後執行,只需要這樣做:
promise.done(do1); // Other code here. promise.done(do2);
這樣可要自由多了,我只要保存這個Promise對象,就在寫代碼的任何時候,給它附加任意數量的回調,而不用管這個非同步事件是在哪裡發起的。這就是Promise的優勢。
正式的介紹
Promise應對非同步操作是如此有用,以至於發展為了CommonJS的一個規範,叫做Promises/A。 Promise代表的是某一作業結束後的回傳值,它有3種狀態:
肯定(fulfilled或resolved),顯示該Promise的操作成功了。
否定(rejected或failed),表示該Promise的操作失敗了。
等待(pending),還沒有得到肯定或否定的結果,進行中。
此外,還有1種名義上的狀態用來表示Promise的操作已經成功或失敗,也就是肯定和否定狀態的集合,叫做結束(settled)。 Promise還具有以下重要的特性:
一个Promise只能从等待状态转变为肯定或否定状态一次,一旦转变为肯定或否定状态,就再也不会改变状态。
如果在一个Promise结束(成功或失败,同前面的说明)后,添加针对成功或失败的回调,则回调函数会立即执行。
想想Ajax操作,发起一个请求后,等待着,然后成功收到返回或出现错误(失败)。这是否和Promise相当一致?
进一步解释Promise的特性还有一个很好的例子:jQuery的$(document).ready(onReady)。其中onReady回调函数会在DOM就绪后执行,但有趣的是,如果在执行到这句代码之前,DOM就已经就绪了,那么onReady会立即执行,没有任何延迟(也就是说,是同步的)。
Promise示例
生成Promise
Promises/A里列出了一系列实现了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,且可以向回调传递任意数据。
合并Promise
还记得我前文说的同时发送2个Ajax请求的难题吗?继续以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失败。
级联Promise
再继续我前文的依次执行一系列异步任务的问题。它将用到Promise最为重要的.then()方法(在Promises/A规范中,也是用“有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的级联。
要级联Promise,需要注意的是,在传递给then()的回调函数中,一定要返回你想要的代表下一步任务的Promise(如上面代码的$.ajax(url2, data))。这样,前面被赋值的那个变量才会变成新的Promise。而如果then()的回调函数返回的不是Promise,则then()方法会返回最初的那个Promise。
应该会觉得有些难理解?从代码执行的角度上说,上面这段带有多个then()的代码其实还是被JavaScript引擎运行一遍就结束。但它就像是写好的舞台剧的剧本一样,读过一遍后,JavaScript引擎就会在未来的时刻,依次安排演员按照剧本来演出,而演出都是异步的。then()方法就是让你能写出异步剧本的笔。
将Promise用在基于回调函数的API
前文反复用到的$.ajax()方法会返回一个Promise对象,这其实只是jQuery特意提供的福利。实际情况是,大多数JavaScript API,包括Node.js中的原生函数,都基于回调函数,而不是基于Promise。这种情况下使用Promise会需要自行做一些加工。
这个加工其实比较简单和直接,下面是例子:
var deferred = $.Deferred(); setTimeout(deferred.resolve, 1000); deferred.done(onDone);
这样,将Promise的肯定或否定的触发器,作为API的回调传入,就变成了Promise的处理模式了。
Promise是怎么实现出来的?
本文写Promise写到这里,你发现了全都是基于已有的实现了Promise的库。那么,如果要自行构筑一个Promise的话呢?
位列于Promises/A的库列表第一位的Q可以算是最符合Promises/A规范且相当直观的实现。如果你想了解如何做出一个Promise,可以参考Q提供的设计模式解析。
限于篇幅,本文只介绍Promise的应用。我会在以后单独开一篇文章来详述Promise的实现细节。
作为JavaScript后续版本的ECMAScript 6将原生提供Promise,如果你想知道它的用法,推荐阅读JavaScript Promises: There and back again。
结语
Promise這個字頑強到不適合翻譯,一眼之下都會覺得意義不明。不過,在JavaScript裡做比較複雜的非同步任務時,它的確可以提供相當多的幫助。
以上是非同步JavaScript程式設計中的Promise使用方法_node.js?1.1.5的內容,更多相關內容請關注PHP中文網(www.php.cn)!

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

AI Hentai Generator
免費產生 AI 無盡。

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

熱門話題

如何使用WebSocket和JavaScript實現線上語音辨識系統引言:隨著科技的不斷發展,語音辨識技術已成為了人工智慧領域的重要組成部分。而基於WebSocket和JavaScript實現的線上語音辨識系統,具備了低延遲、即時性和跨平台的特點,成為了廣泛應用的解決方案。本文將介紹如何使用WebSocket和JavaScript來實現線上語音辨識系

WebSocket與JavaScript:實現即時監控系統的關鍵技術引言:隨著互聯網技術的快速發展,即時監控系統在各個領域中得到了廣泛的應用。而實現即時監控的關鍵技術之一就是WebSocket與JavaScript的結合使用。本文將介紹WebSocket與JavaScript在即時監控系統中的應用,並給出程式碼範例,詳細解釋其實作原理。一、WebSocket技

如何利用JavaScript和WebSocket實現即時線上點餐系統介紹:隨著網路的普及和技術的進步,越來越多的餐廳開始提供線上點餐服務。為了實現即時線上點餐系統,我們可以利用JavaScript和WebSocket技術。 WebSocket是一種基於TCP協定的全雙工通訊協議,可實現客戶端與伺服器的即時雙向通訊。在即時線上點餐系統中,當使用者選擇菜餚並下訂單

如何使用WebSocket和JavaScript實現線上預約系統在當今數位化的時代,越來越多的業務和服務都需要提供線上預約功能。而實現一個高效、即時的線上預約系統是至關重要的。本文將介紹如何使用WebSocket和JavaScript來實作一個線上預約系統,並提供具體的程式碼範例。一、什麼是WebSocketWebSocket是一種在單一TCP連線上進行全雙工

在日常生活中,我們常常會遇到承諾與兌現之間的問題。無論是在個人關係中,或是在商業交易中,承諾的兌現都是建立信任的關鍵。然而,承諾的利與弊也常常會引起爭議。本文將探討承諾的利與弊,並給予一些建議,如何做到言出必行。承諾的利是顯而易見的。首先,承諾可以建立信任。當一個人信守承諾時,他會讓別人相信自己是個可信賴的人。信任是人與人之間建立的紐帶,它可以讓人們更加

JavaScript和WebSocket:打造高效的即時天氣預報系統引言:如今,天氣預報的準確性對於日常生活以及決策制定具有重要意義。隨著技術的發展,我們可以透過即時獲取天氣數據來提供更準確可靠的天氣預報。在本文中,我們將學習如何使用JavaScript和WebSocket技術,來建立一個高效的即時天氣預報系統。本文將透過具體的程式碼範例來展示實現的過程。 We

JavaScript教學:如何取得HTTP狀態碼,需要具體程式碼範例前言:在Web開發中,經常會涉及到與伺服器進行資料互動的場景。在與伺服器進行通訊時,我們經常需要取得傳回的HTTP狀態碼來判斷操作是否成功,並根據不同的狀態碼來進行對應的處理。本篇文章將教你如何使用JavaScript來取得HTTP狀態碼,並提供一些實用的程式碼範例。使用XMLHttpRequest

Promise.resolve()詳解,需要具體程式碼範例Promise是JavaScript中一種用來處理非同步操作的機制。在實際開發中,常常需要處理一些需要依序執行的非同步任務,而Promise.resolve()方法就是用來傳回一個已經Fulfilled狀態的Promise物件。 Promise.resolve()是Promise類別的靜態方法,它接受一個
