ES6 Promise 應用: 回撥函式方法封裝成 Promise + async/await 同步化
ES6 Promise 應用: 回撥函式方法封裝成 Promise + async/await 同步化
文章目錄
簡介
前一篇ES6特性:Promise非同步函式介紹了 ES6 的新特性 Promise 物件,而ES6 實戰: 手寫 Promise則是嘗試使用 ES5 以下的語法手擼一個 Promise 出來。
本篇則是要來介紹一種 Promise 物件的應用:將接受回撥函式的方法包裝成 Promise 物件,同時透過 async/await 關鍵字同步化。屬於實際開發時非常常見的需求,也是非常實用的技能。
參考
完整示例程式碼
https://github.com/superfreeeee/Blog-code/tree/main/front_end/es6/es6_promise_encapsulation_callback
正文
什麼是"接受回撥函式的方法"?
我們在開發時使用一些 ES5 以前的庫或是不支援 ES6 以上的版本環境時,常常需要用到接受回撥函式的方法呼叫,常見的例子有以下幾個:
示例一:http 請求
- http 請求(使用
request
第三方庫)
const request = require('request')
request.get('http://localhost:3000', (err, res, body) => {/* http 請求結果處理 */})
request.get
方法會發起一個 http 請求,當請求返回時回撥用第二個引數的處理函式對返回結果進行處理
示例二:mysql 命令
- mysql 連線、查詢(使用
mysql
庫)
const mysql = require('mysql')
const connection = mysql.createConnection({/* 配置資訊省略 */})
connection.query('select * from a_table', (err, data) => {/* sql 查詢結果處理 */})
類似的,透過 mysql
庫能夠執行 SQL 命令,並且在命令執行完畢後呼叫第二個引數的回撥函式進行處理
示例三:同步方法
這邊我們要解開一個常見的迷思:接受回撥函式的方法並不一定要是非同步函式
,前兩個例子確實會進行非同步呼叫後才執行回撥函式處理非同步呼叫返回的結果,但是像下面這個函式也是一種回撥函式方法
- 同步方法接受回撥函式
const syncFunction = (x, cb) => {
console.log(`param x = ${x}`)
cb()
}
syncFunction(1, () => {/* do something */})
面臨問題:回撥地獄
乍看之下,其實回撥函式好像還挺合理的,透過接受回撥函式的方法將方法結果處理的邏輯
交給呼叫者決定(藉由回撥函式 callback)。然而這樣會引發回撥地獄的慘劇,試想如果我們呼叫 sql 查詢時想要根據不同的結果再進行二次甚至三次的查詢會發生什麼事:
connection.query('' /* SQL命令1 */, (err, data1) => {
/* judge or do something else */
connection.query('' /* SQL命令2 */, (err, data2) => {
/* judge or do something else */
connection.query('' /* SQL命令3 */, (err, data3) => {
/* judge or do something else */
console.log(data3)
})
})
})
這樣一來不僅程式碼結構會變得異常複雜而且醜陋,同時業務的邏輯也會顯得混亂不堪,容易忘記哪些指令生處於哪一層的查詢之中,第三個問題是查詢結果的處理也異常困難。由於 data 都是屬於回撥函
數的區域性變數
,所以透過 return 返回結果是沒有意義的,而回撥函式以外的部分也沒辦法獲取到查詢的結果(因為呼叫邏輯可能是非同步的,而查詢語句前後的上下文為同步呼叫的環境)。
為此,我們就能夠利用 ES6 提供的 Promise 物件來進行回撥函式的封裝,將層層包裹的 callback 函式解放,變為使用 then
方法的鏈式呼叫
形式
封裝開始
接下來我們就要來介紹如何將回撥函式方法封裝成 Promise 物件,並透過 async/await 將非同步方法同步化
回撥函式方法準備(接受回撥函式的方法)
這邊我們使用 request
第三方庫來發起 http 請求作為非同步方法的代表
const request = require('request')
// callback function
function normalCallback (test, cb) {
console.log(`invoke function f from test ${test}`)
request.get('http://localhost:3000', (err, res, body) => {
cb(err, body)
})
}
normalCallback
方法主要功能是向 3000 埠的預設路由請求服務,收到結果後執行回撥函式處理結果
一般情況下我們使用上面這種回撥函式方法最直白的方式就是按引數傳入回撥函式(結果處理函式),如下
const test1 = () => {
// 最原始的回撥函式使用方式
normalCallback('test1', (err, data) => {
if (err) {
console.log('err occur')
} else {
console.log(`receive data ${data}`)
}
})
}
test1()
輸出
invoke function f from test test1
receive data Hello World
使用 Promise 進行封裝
第二步我們要將回撥函式方法封裝成 Promise 物件
const generatePromise = (test) => {
return new Promise(function (resolve, reject) {
normalCallback(test, (err, data) => {
if (err) {
reject(err)
} else {
resolve(data)
}
})
})
}
透過將原方法作為 Promise 任務的主體,並在回撥函式中呼叫 resolve/reject 方法改變 Promise 的狀態。
如此一來我們就可以像下面這樣透過 then 函式來依序傳入結果處理函式
const test2 = () => {
// 呼叫封裝好的方法返回 Promise 物件
generatePromise('test2')
.then(res => {
console.log(`receive data ${res}`)
})
.catch(err => {
console.log('err occur')
})
}
test2()
輸出
invoke function f from test test2
receive data Hello World
使用 async/await 關鍵字同步化
到此我們還不滿足,Proimse 的鏈式呼叫是比 callback 的使用形式好看不少,但是與一般的同步方法還是存在一定的差異,接下來我們使用 async/await 關鍵字進行方法同步化
async function asyncUsage () {
const res = await generatePromise('test3')
console.log(`receive data ${res}`)
}
透過在外再包裹一層非同步方法(async 關鍵字),非同步方法內部就能夠使用 await 方法進行同步化,直接獲取 Promise 物件的返回值 res,如此一來這樣的呼叫形式就跟一般的同步方法一樣,這就是我們想要的最終形態。
最後看一下呼叫結果
const test3 = () => {
asyncUsage()
}
test3()
輸出
invoke function f from test test3
receive data Hello World
注意點:如果非同步方法報錯,await
關鍵字是沒辦法處理會直接再向外丟擲異常,所以通常需要在某個層次上使用 try...catch
塊來捕捉異常,避免程式退出
結語
本篇的核心目標在於將 callback 的呼叫形式封裝成 Promise 的鏈式呼叫(then、catch),最後同步化,是實戰開發時不可或缺的能力。
相關文章
- 回撥函式到promise再到理解async/await函式PromiseAI
- JavaScript非同步程式設計史:回撥函式到Promise到Async/AwaitJavaScript非同步程式設計函式PromiseAI
- async await函式效能與Promise併發AI函式Promise
- Promise(es6)和await,async(es7)PromiseAI
- [Javascript] Promise question with async awaitJavaScriptPromiseAI
- JS 非同步發展流程(回撥函式=>Async/await)JS非同步函式AI
- async/await 和 promise/promise.all 的示例AIPromise
- 使用promise封裝jquery的ajax來實現async和await方式Promise封裝jQueryAI
- Promise/async/await 研究筆記PromiseAI筆記
- Promise與async/await與GeneratorPromiseAI
- Async/Await 代替 Promise.all()AIPromise
- Promise和async await詳解PromiseAI
- ES6 Async/Await 完爆Promise的6個原因AIPromise
- 手把手教你Node使用Promise替代回撥函式Promise函式
- Generator與Promise的完美結合 -- async await函式誕生記PromiseAI函式
- 用Promise建構函式來解決地獄回撥問題Promise函式
- Promise && async/await的理解和用法PromiseAI
- 重構:從Promise到Async/AwaitPromiseAI
- 應用Promise封裝Ajax實踐Promise封裝
- vue3 專用 indexedDB 封裝庫,基於Promise告別回撥地獄VueIndex封裝Promise
- promise以及async、await學習總結PromiseAI
- Async/Await替代Promise的6個理由AIPromise
- Promise, Generator, async/await的漸進理解PromiseAI
- 理解koa2 之 async + await + promiseAIPromise
- async await、Promise、setTimeout執行順序AIPromise
- ES6(四)用Promise封裝一下IndexedDBPromise封裝Index
- 分享:用promise封裝ajaxPromise封裝
- 現代 JS 流程控制:從回撥函式到 Promises 再到 Async/AwaitJS函式PromiseAI
- 面試向:Async/Await 代替 Promise.all()面試AIPromise
- 原生es6封裝一個Promise物件封裝Promise物件
- 用promise封裝一個ajaxPromise封裝
- jsonp promise 封裝JSONPromise封裝
- 深入理解 promise、generator+co、async/await 用法PromiseAI
- 理解非同步之美:Promise與async await(一)非同步PromiseAI
- 理解非同步之美:Promise 與 async await(二)非同步PromiseAI
- 使用async await 封裝 axiosAI封裝iOS
- 解析Promise解決非同步回撥Promise非同步
- 【理解ES7async/await並實現】手把手進行ES6非同步程式設計:Generator + Promise = Async/AwaitAI非同步程式設計Promise