摘要:一是如何鏈?zhǔn)秸{(diào)用,二是如何中止鏈?zhǔn)秸{(diào)用。到目前為止,我們就基本了解了的用法及特點,并實現(xiàn)用重構(gòu)用回調(diào)函數(shù)寫的異步操作。
Abstract
本文主要講的是如何實現(xiàn) Promise 的鏈?zhǔn)秸{(diào)用。也就是 promise().then().then().catch() 的形式,然后討論如何在某一個 then() 里面中止 Promise。
在程序中,只要返回了一個 promise 對象,如果 promise 對象不是 Rejected 或 Fulfilled 狀態(tài),then 方法就會繼續(xù)調(diào)用。利用這個特性,可以處理多個異步邏輯。但有時候某個 then 方法的執(zhí)行結(jié)果可能會決定是否需要執(zhí)行下一個 then,這個時候就需中止 promise,主要思想就是使用 reject 來中止 promise 的 then 繼續(xù)執(zhí)行。
“中止”這個詞不知道用得是否準(zhǔn)確。這里可能還是 break 的含義更精確,跳出本次 promise,不繼續(xù)執(zhí)行后面的 then 方法。但 promise 依舊會繼續(xù)執(zhí)行。
Can I use promises當(dāng)前瀏覽器對 Promise 的支持情況見下圖:
http://caniuse.com/#search=promise
Promise先簡單復(fù)習(xí)一下 Promise。Promise 其實很簡單,就是一個處理異步的方法。一般可以通過 new 方法來調(diào)用 Promise 的構(gòu)造器實例化一個 promise 對象:
var promise = new Promise((resolve, reject) => { // 異步處理 // 處理結(jié)束后,調(diào)用 resolve 或 reject // 成功時就調(diào)用 resolve // 失敗時就調(diào)用 reject });
用 new Promise 實例化的 promise 對象有以下三個狀態(tài):
"has-resolution" - Fulfilled。resolve(成功)時,此時會調(diào)用 onFulfilled
"has-rejection" - Rejected。reject(失敗)時,此時會調(diào)用 onRejected
"unresolved" - Pending。既不是resolve也不是reject的狀態(tài),也就是promise對象剛被創(chuàng)建后的初始化狀態(tài)等
關(guān)于上面這三種狀態(tài)的讀法,其中左側(cè)為在 ES6 Promises 規(guī)范中定義的術(shù)語, 而右側(cè)則是在 Promises/A+ 中描述狀態(tài)的術(shù)語。基本上狀態(tài)在代碼中是不會涉及到的,所以名稱也無需太在意。
Promise Chain先來假設(shè)一個業(yè)務(wù)需求:在系統(tǒng)中使用教務(wù)系統(tǒng)賬號進行登錄。首先用戶在登錄頁面輸入用戶名(教務(wù)系統(tǒng)賬號)和密碼(教務(wù)系統(tǒng)密碼);然后判斷數(shù)據(jù)庫中是否存在該用戶;如果不存在則使用用戶名和密碼模擬登錄教務(wù)系統(tǒng),如果模擬登錄成功,則存儲用戶名和密碼,并返回登錄成功。
聽起來就有點復(fù)雜對不對?于是畫了個流程圖來解釋整個業(yè)務(wù)邏輯:
上圖只是一個簡化版本,比如密碼加密、session設(shè)置等沒有表現(xiàn)出來,大家知道就好。圖中 (1)、(2)、(3) 三個地方就是會進行異步處理的地方,一般數(shù)據(jù)庫操作、網(wǎng)絡(luò)請求都是異步的。
如果用傳統(tǒng)的回調(diào)函數(shù) callback 來處理上面的邏輯,嵌套的層級就會比較深,上面的業(yè)務(wù)因為有三個異步操作所以有三層回調(diào),代碼大概會是下面的樣子:
// 根據(jù) name 查詢用戶信息 findUserByName(name, function(err, userinfo) { if (err) { return res.json({ code: 1000, message: "查詢用戶信息,數(shù)據(jù)庫操作數(shù)出現(xiàn)異常", }); } if (userinfo.length > 0) { // 用戶存在 if (userinfo[0].pwd === pwd) // 密碼正確 return res.json({ code: 0, message: "登錄成功", }); } // 數(shù)據(jù)庫中不存在該用戶,模擬登錄教務(wù)系統(tǒng) loginEducationSystem(name, pwd, function(err, result) { if (err) { return res.json({ code: 1001, message: "模擬登錄教務(wù)系統(tǒng)出現(xiàn)異常", }); } // 約定正確情況下,code 為 0 if (result.code !== 0) { return res.json({ code: 1002, message: "模擬登錄教務(wù)系統(tǒng)失敗,可能是用戶名或密碼錯誤", }); } // 模擬登錄成功,將用戶名密碼存入數(shù)據(jù)庫 saveUserToDB(name, pwd, function(err, result) { if (err) { return res.json({ code: 1003, message: "將用戶名密碼存入數(shù)據(jù)庫出現(xiàn)異常", }); } if (result.code !== 0) { return res.json({ code: 1004, message: "將用戶名密碼存入數(shù)據(jù)庫出現(xiàn)異常", }); } return res.json({ code: 0, message: "登錄成功!", }); }); }); });
上面的代碼可能存在的不優(yōu)雅之處:
隨著業(yè)務(wù)邏輯變負(fù)責(zé),回調(diào)層級會越來越深
代碼耦合度比較高,不易修改
每一步操作都需要手動進行異常處理,比較麻煩
接下來再用 promise 實現(xiàn)此處的業(yè)務(wù)需求。使用 promise 編碼之前,可以先思考兩個問題。
一是如何鏈?zhǔn)秸{(diào)用,二是如何中止鏈?zhǔn)秸{(diào)用。
How to Use Promise Chain業(yè)務(wù)中有三個需要異步處理的功能,所以會分別實例化三個 promise 對象,然后對 promise 進行鏈?zhǔn)秸{(diào)用。那么,如何進行鏈?zhǔn)秸{(diào)用?
其實也很簡單,直接在 promise 的 then 方法里面返回另一個 promise 即可。例如:
function start() { return new Promise((resolve, reject) => { resolve("start"); }); } start() .then(data => { // promise start console.log("result of start: ", data); return Promise.resolve(1); // p1 }) .then(data => { // promise p1 console.log("result of p1: ", data); return Promise.reject(2); // p2 }) .then(data => { // promise p2 console.log("result of p2: ", data); return Promise.resolve(3); // p3 }) .catch(ex => { // promise p3 console.log("ex: ", ex); return Promise.resolve(4); // p4 }) .then(data => { // promise p4 console.log("result of p4: ", data); });
上面的代碼最終會輸出:
result of start: start result of p1: 1 ex: 2 result of p4: 4
代碼的執(zhí)行邏輯如圖:
從圖中可以看出來,代碼的執(zhí)行邏輯是 promise start --> promise p1 --> promise p3 --> promise p4。所以結(jié)合輸出結(jié)果和執(zhí)行邏輯圖,總結(jié)出以下幾點:
promise 的 then 方法里面可以繼續(xù)返回一個新的 promise 對象
下一個 then 方法的參數(shù)是上一個 promise 對象的 resolve 參數(shù)
catch 方法的參數(shù)是其之前某個 promise 對象的 rejecte 參數(shù)
一旦某個 then 方法里面的 promise 狀態(tài)改變?yōu)榱?rejected,則promise 方法連會跳過后面的 then 直接執(zhí)行 catch
catch 方法里面依舊可以返回一個新的 promise 對象
How to Break Promise Chain接下來就該討論如何中止 promise 方法鏈了。
通過上面的例子,我們可以知道 promise 的狀態(tài)改變?yōu)?rejected 后,promise 就會跳過后面的 then 方法。
也就是,某個 then 里面發(fā)生異常后,就會跳過 then 方法,直接執(zhí)行 catch。
所以,當(dāng)在構(gòu)造的 promise 方法鏈中,如果在某個 then 后面,不需要再執(zhí)行 then 方法了,就可以把它當(dāng)作一個異常來處理,返回一個異常信息給 catch,其參數(shù)可自定義,比如該異常的參數(shù)信息為 { notRealPromiseException: true},然后在 catch 里面判斷一下 notRealPromiseException 是否為 true,如果為 true,就說明不是程序出現(xiàn)異常,而是在正常邏輯里面中止 then 方法的執(zhí)行。
代碼大概就這樣:
start() .then(data => { // promise start console.log("result of start: ", data); return Promise.resolve(1); // p1 ) .then(data => { // promise p1 console.log("result of p1: ", data); return Promise.reject({ notRealPromiseException: true, }); // p2 }) .then(data => { // promise p2 console.log("result of p2: ", data); return Promise.resolve(3); // p3 }) .catch(ex => { console.log("ex: ", ex); if (ex.notRealPromiseException) { // 一切正常,只是通過 catch 方法來中止 promise chain // 也就是中止 promise p2 的執(zhí)行 return true; } // 真正發(fā)生異常 return false; });
這樣的做法可能不符合 catch 的語義。不過從某種意義上來說,promise 方法鏈沒有繼續(xù)執(zhí)行,也可以算是一種“異?!薄?/p> Refactor Callback with Promise
講了那么多道理,現(xiàn)在就改來使用 promise 重構(gòu)之前用回調(diào)函數(shù)寫的異步邏輯了。
// 據(jù) name 查詢用戶信息 const findUserByName = (name, pwd) => { return new Promise((resolve, reject) => { // 數(shù)據(jù)庫查詢操作 if (dbError) { // 數(shù)據(jù)庫查詢出錯,將 promise 設(shè)置為 rejected reject({ code: 1000, message: "查詢用戶信息,數(shù)據(jù)庫操作數(shù)出現(xiàn)異常", }); } // 將查詢結(jié)果賦給 userinfo 變量 if (userinfo.length === 0) { // 數(shù)據(jù)庫中不存在該用戶 resolve(); } // 數(shù)據(jù)庫存在該用戶,判斷密碼是否正確 if (pwd === userinfo[0].pwd) { // 密碼正確,中止 promise 執(zhí)行 reject({ notRealPromiseException: true, data: { code: 0, message: "密碼正確,登錄成功", } }); } // 密碼不正確,登錄失敗,將 Promise 設(shè)置為 Rejected 狀態(tài) reject({ code: 1001, message: "密碼不正確,登錄失敗", }); }); }; // 模擬登錄教務(wù)系統(tǒng) const loginEducationSystem = (name, pwd) => { // 登錄邏輯... // 登錄成功 resolve(); // 登錄失敗 reject({ code: 1002, message: "模擬登錄教務(wù)系統(tǒng)失敗", }); }; // 將用戶名密碼存入數(shù)據(jù)庫 const saveUserToDB(name, pwd) => { // 數(shù)據(jù)庫存儲操作 if (dbError) { // 數(shù)據(jù)庫存儲出錯,將 promise 設(shè)置為 rejected reject({ code: 1004, message: "數(shù)據(jù)庫存儲出錯,將出現(xiàn)異常", }); } // 數(shù)據(jù)庫存儲操作成功 resolve(); }; findUserByName(name) .then(() => { return loginEducationSystem(name, pwd); }) .then(() => { return saveUserToDB(name, pwd); }) .catch(e => { // 判斷異常出現(xiàn)原因 if (e.notRealPromiseException) { // 正常中止 promise 而故意設(shè)置的異常 return res.json(e.data); } // 出現(xiàn)錯誤或異常 return res.json(e); });
在上面的代碼中,實例化了三個 promise 對象,分別實現(xiàn)業(yè)務(wù)需求中的三個功能。然后通過 promise 方法鏈來調(diào)用。相比用回調(diào)函數(shù)而言,代碼結(jié)構(gòu)更加清晰,也更易讀易懂耦合度更低更易擴展了。
Promise.all && Promise.race仔細(xì)觀察可以發(fā)現(xiàn),在上面的 promise 代碼中,loginEducationSystem 和 saveUserToDB 兩個方法執(zhí)行有先后順序要求,但沒有數(shù)據(jù)傳遞。
其實 promise 方法鏈更好用的一點是,當(dāng)下一個操作依賴于上一個操作的結(jié)果的時候,可以很方便地通過 then 方法的參數(shù)來傳遞數(shù)據(jù)。前面頁提到過,下一個 then 方法的參數(shù)就是上一個 then 方法里面 resolve 的參數(shù),所以當(dāng)然就可以把上一個 then 方法的執(zhí)行結(jié)果作為參數(shù)傳遞給下一個 then 方法了。
還有些時候,可能 then 方法的執(zhí)行順序也沒有太多要求,只需要 promise 方法鏈中的兩個或多個 promise 全部都執(zhí)行正確。這時,如果依舊一個一個去寫 then 可能就比較麻煩,比如:
function p1() { return new Promise((resolve) => { console.log(1); resolve(); }); } function p2() { return new Promise((resolve) => { console.log(2); resolve(); }); } function p3() { return new Promise((resolve) => { console.log(3); resolve(); }); }
現(xiàn)在只需要 p1 p2 p3 這三個 promise 都執(zhí)行,并且 promise 最終狀態(tài)都是 Fulfilled,那么如果還是使用方法鏈,這是這樣調(diào)用:
p1() .then(() => { return p2(); }) .then(() => { return p3(); }) .then(() => { console.log("all done"); }) .catch(e => { console.log("e: ", e); }); // 輸出結(jié)果: // 1 // 2 // 3 // all done
代碼貌似就不那么精煉了。這個時候就有了 Promise.all 這個方法。
Promise.all 接收一個 promise對象的數(shù)組作為參數(shù),當(dāng)這個數(shù)組里的所有 promise 對象全部變?yōu)?resolve 或 reject 狀態(tài)的時候,它才會去調(diào)用 then 方法。
于是,調(diào)用這幾個 promise 的代碼就可以這樣寫了:
p1() .then(() => { return Promise.all([ p2(), p3(), ]); }) .then(() => { console.log("all done"); }) .catch((e) => { console.log("e: ", e); }); // 輸出結(jié)果: // 1 // 2 // 3 // all done
這樣看起來貌似就精煉些了。
而對于 Promise.race,其參數(shù)也跟 Promise.all 一樣是一個數(shù)組。只是數(shù)組中的任何一個 promise 對象如果變?yōu)?resolve 或者reject 的話,該函數(shù)就會返回,并使用這個 promise 對象的值進行 resolve 或者 reject。
這里就不舉例了。
Conclusion到目前為止,我們就基本了解了 Promise 的用法及特點,并實現(xiàn)用 Promise 重構(gòu)用回調(diào)函數(shù)寫的異步操作?,F(xiàn)在對 Promise 的使用,應(yīng)該駕輕就熟了。
完。
Github Issue: https://github.com/nodejh/nodejh.github.io/issues/23
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/81048.html
摘要:使用對象的好處在于可以將異步操作以同步操作的流程表達出來,避免了層層嵌套的回調(diào)函數(shù)。對象異步操作拋出錯誤,狀態(tài)就會變?yōu)?,就會調(diào)用方法指定的回調(diào)函數(shù)處理這個錯誤。 Promise 含義 Promise 是異步編程的一種解決方案,比傳統(tǒng)的解決方案——回調(diào)函數(shù)和事件——更合理和更強大。它由社區(qū)最早提出和實現(xiàn),ES6 將其寫進了語言標(biāo)準(zhǔn),統(tǒng)一了用法,原生提供了 Promise 對象。 所謂 P...
摘要:的和我們通過的原型方法拿到我們的返回值輸出我延遲了毫秒后輸出的輸出下列的值我延遲了毫秒后輸出的。有人說,我不想耦合性這么高,想先執(zhí)行函數(shù)再執(zhí)行,但不想用上面那種寫法,可以嗎,答案是當(dāng)然可以。 此文只介紹Async/Await與Promise基礎(chǔ)知識與實際用到注意的問題,將通過很多代碼實例進行說明,兩個實例代碼是setDelay和setDelaySecond。 tips:本文系原創(chuàng)轉(zhuǎn)自...
摘要:綜上,對進行一定的封裝,來簡化編碼操作?;膰L試對于這種帶大量回調(diào)的,使用進行異步化封裝是個好主意。因此包括在內(nèi)的所有異步方法都會強制中止當(dāng)前事務(wù)。這就決定了一個事務(wù)內(nèi)部的所有操作必須是同步完成的。目前只實現(xiàn)了和,其他的有待下一步工作。 前言 本文是介紹我在編寫indexedDB封裝庫中誕生的一個副產(chǎn)品——如何讓indexedDB在支持鏈?zhǔn)秸{(diào)用的同時,保持對事務(wù)的支持。項目地址:htt...
摘要:參數(shù)如前面所提到的,方法只是方法的一個語法糖,原因就在于方法的參數(shù)為實際上是兩個回調(diào)函數(shù),分別用于處理調(diào)用它的對象的和狀態(tài),而方法就等價于狀態(tài)處理函數(shù)。對象狀態(tài)傳遞和改變的方法利用回調(diào)的返回值,可以控制某個操作后方法返回的對象及其狀態(tài)。 注意,本文主要針對ES6標(biāo)準(zhǔn)實現(xiàn)的Promise語法進行闡述,實例代碼也都使用ES6語法,快速入門ES6請參見ECMAScript 6 掃盲。 一分鐘...
摘要:前言使用中,鏈?zhǔn)降恼{(diào)用對于控制異步執(zhí)行很重要。的鏈?zhǔn)秸{(diào)用是支持鏈?zhǔn)秸{(diào)用的,但是它是不同于上面的鏈?zhǔn)?。是調(diào)用方法返回自身,但是是調(diào)用方法后返回一個新的。的運行機制請參考的運行機制值穿透由于通過沒有成功添加回調(diào)函數(shù),發(fā)生了值穿透。 前言 使用Promise中,鏈?zhǔn)降恼{(diào)用對于控制異步執(zhí)行很重要。 鏈?zhǔn)秸{(diào)用 在jQuery的使用中,我們常常使用下面的代碼 $(#app).show().css(...
閱讀 2809·2021-10-08 10:04
閱讀 3198·2021-09-10 11:20
閱讀 522·2019-08-30 10:54
閱讀 3305·2019-08-29 17:25
閱讀 2301·2019-08-29 16:24
閱讀 883·2019-08-29 12:26
閱讀 1446·2019-08-23 18:35
閱讀 1929·2019-08-23 17:53