彭彭直播-回呼函式 Callbacks、Promises 物件、Async/Await 非同步流程控制

Huang Pei
6 min readFeb 21, 2020

--

對於這一塊觀念一直不是很熟稔,所以參考彭彭老師的影片寫紀錄,若有錯誤歡迎指正。

程式碼:github

影片:回呼函式 Callbacks、Promises 物件、Async/Await 非同步流程控制 — 彭彭直播 at 2019/04/07

問題起源:非同步的程式碼,常見情況有網路連線、setTimeout排程、檔案讀取等。

設定排程,延遲一段時間後執行,期望能延遲2秒鐘獲得加法結果,然而console 的順序結果分別會是 '1', undefined, '2',無法順利讀取顯示 console 結果。

function delayedAdd(n1, n2, delayTime) {
window.setTimeout(function() {
console.log('2')
return n1 + n2;}, delayTime)
console.log('1')}
function test() {
let result = delayedAdd(3, 4, 2000);
console.log(result);} //undefined
test()

關於為什麼 console.log(result) 的結果會是undefined,參考之前寫的文章1文章2,簡單來說 setTimeout 被丟到 Event Queue(事件佇列)中,其他的程式碼都跑完了才會執行 setTimeout 內的內容,因此先執行的console.log(result) 抓不到結果,故為 undefined。

解決非同步行為的三種方式:

  1. Callback 回呼函數:最傳統的方法,會有 callback hell 的情況。

將欲後執行的函式,也就是印出 console.log(result) 的函數作為參數傳入delayedAdd,並在 setTimeout 中傳入呼叫 callback 函式,將加法的結果傳遞到callback的函式之中,再執行該函式。

function delayedAdd(n1, n2, delayTime, callback) {
window.setTimeout(function() {
let ans = n1 + n2 // 先執行主要的加法任務
callback(ans)} // 再呼叫 callback 將結果回傳
, delayTime)}
function test() {
delayedAdd(3, 4, 2000,
function(result) {
console.log(result)})} //7
test()

簡化的例子:

let a = function (callback) {  
setTimeout(() => {
console.log(‘a function’); //先印‘a function’
callback()}, 2000);} //叫b
let b = function () {
console.log(‘b function’);} //再印‘b function’
a(b)// 2秒後 ‘a function’
// 2秒後 ‘b function’

*關於 callback function 推薦參考這篇文章:重新認識 JavaScript: Day 18 Callback Function 與 IIFE

2. Promise 物件

首先,建立 Promise 物件:new Promise(執行函式),將想要做的工作放到 Promise 中,並回傳整個 Promise 的內容。

function delayedAdd(n1, n2, delayTime) {  return new Promise(    function(resolve, reject) {
window.setTimeout(
function() {
resolve(n1 + n2)},delayTime)}
)
}

之後,帶入參數呼叫 Promise,成功得到了結果 result 後, 再使用 then 執行下一步,若回傳失敗則會呼叫 reject() 並執行 catch 處理錯誤。

function test(){  let promise = delayedAdd(3, 4, 2000);  promise
.then(result => console.log(result)) // 7
.catch(error => console.log(error)) // 'Err'
}
test()

Promise.all()分別等待多個 Promise 完成後才繼續動作

假設要做兩次的加法,等相加完成後進行乘法,可使用Promise.all 進行整合。

傳入參數為陣列,陣列內容是所有希望完成的 Promise,而回傳的結果也會是對應的結果陣列。

function test() {  let promise1 = delayedAdd(3, 4, 2000);
let promise2 = delayedAdd(2, 3, 3000);
Promise.all([promise1, promise2])
.then(results => { //results=[7,5]
results.reduce((acc, cur) => console.log(acc * cur))}) //35
}test()

3. Async / Await 簡化 Promise 的語法糖

回傳 Promise 的物件的大前提不變下:

function delayedAdd(n1, n2, delayTime) {  return new Promise(    function(resolve, reject) {
window.setTimeout(
function() {
resolve(n1 + n2)},delayTime)}
)
}

await 簡化改善了Promise的呼叫方式。

在呼叫的前方加上await,promise回傳的結果會直接賦予在 result 上,如果函式中使用了 await,函式則必須加上 async 宣告這是 async 函式。

程式碼會在 await 的地方進行等待 (pending) ,直到Promise的值回來才會繼續執行await後面的程式碼,因此 ’Hello’ 不會馬上出現,而是會等 result 回傳後才一起出現。

async function test() {  let result = await delayedAdd(3, 4, 2000);
console.log('Hello') // 2 秒後同時顯示 Hello 與 7
console.log(result)}
test()

若有多筆 Promise 要處理,使用 async/await 的寫法如下。

若要處理成功與錯誤,則可使用 try…catch 語法。

async function test() {  try{    let result1 = await delayedAdd(3, 4, 2000);
let result2 = await delayedAdd(2, 3, 3000);
console.log(result1 * result2);
} catch(e) { }
}

*async/await 因為會卡住等待(先等2秒處理3+4、再等3秒處理2+5),所以時間上會比 promise 久一點。

另外這幾篇文章利用圖文解釋也滿淺顯易懂的:

ES6 原生 Fetch 遠端資料方法使用 Promise 處理非同步JavaScript Await 與 Async

--

--

Huang Pei
Huang Pei

Written by Huang Pei

記錄用倉庫,歡迎指正。菜鳥前端,最菜的那種(超能力少女です)。

No responses yet