0%

JS 回呼函式 Callbacks、Promises 物件、Async/Await 非同步流程控制

Javascript 近幾年來受歡迎的原因,有一大部分是因為 JS 可以處理非同步(異步)的問題,也因為可以非同步,所以處理速度快,且可帶給瀏覽者更好的體驗,所以了解如何控制非同步也成了前端重要的技能之一。

無非同步的程式

1
2
3
4
5
6
7
8
function add(n1, n2) {
return n1 + n2;
}
function testNoAsync() {
let result = add(3, 4);
console.log(result);
}
// testNoAsync(); // 7

問題的起源,非同步程式

寫程式如果遇到要處理非同步的程式,就會變得複雜了起來,為什麼會複雜?
預設想要延遲執行,卻出現 undefined 程式會一直往下跑, return 的部分不會回傳會被忽略….。

1
2
3
4
5
6
7
8
9
10
11
12
13
function delayedAdd0(n1, n2, delayTime) {
// 設定排程,延遲一段時間後執行
window.setTimeout(function() {
console.log(2);
return n1 + n2;
}, delayTime);
console.log(1); // 仔細觀察出現的順序
}
function test() {
let result = delayedAdd0(4, 2, 2000);
console.log(result);
}
test(); // 1, undefined, 2 (不會出現return結果)

第一個解決方式 : callback 回呼函式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function delayedAdd1(n1, n2, delayTime, callback) {
// callback 函式
// 設定排程,延遲一段時間後執行
window.setTimeout(function() {
// 延遲一段時間,計算加法,呼叫callback函式
callback(n1 + n2);
}, delayTime);
}

function test1() {
delayedAdd1(4, 2, 2000, function(result) {
console.log(result);
});
}
test1(); // 7

第二個解決方式 : Promise 物件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function delayedAdd2(n1, n2, delayTime) {
// 建立 Promise 物件 : new promise(執行函式)
let p = new Promise(function(resolve, reject) {
// 兩個系統給的參數
// 要做的工作放到promise的函式裡
window.setTimeout(function() {
resolve(n1 + n2); // 工作完成,呼叫 resolve 函式,並把結果透過參數傳遞進去,如果工作出現問題就呼叫 reject ex. reject(n1+n2);
}, delayTime);
});
// 把 promise 傳遞出來
return p; // 或直接以 return 去接以上的結果 回傳回去而不需要變數 p
}
// resolve 會對應 .then / reject 會對應 .catch 但.catch可以忽略不寫
function test2() {
let promise = delayedAdd2(4, 2, 2000);
promise
.then(function(result) {
console.log(result);
})
.catch(function(error) {
console.log('Error', error);
});
}

需求: 需要運行兩次的 promise 然後將結果相乘

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function test2a() {
let promise1 = delayedAdd2(4, 2, 2000);
let promise2 = delayedAdd2(1, 5, 2000);
// 多個 promise 都完成後才繼續工作.
// 把 promises 放入陣列裡
Promise.all([promise1, promise2]).then(function(results) {
// 用 reduce() 處理相乘
let answer = results.reduce(function(total, value) {
return total * value;
});
console.log(answer);
});
}
test2a();

第三個解決方式 : 使用 Async/Await 簡化 Promise 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Async/Await 背後的邏輯就是和 Promise 是一樣的。
// 使用 Async/Await 的大前提是仍要建立一個 Promise 物件 將它回傳
function delayedAdd3(n1, n2, delayTime) {
// 建立 Promise 物件 : new promise(執行函式)
return new Promise(function(resolve, reject) {
// 兩個系統給的參數
// 要做的工作放到promise的函式裡
window.setTimeout(function() {
resolve(n1 + n2); // 工作完成,呼叫 resolve 函式,並把結果透過參數傳遞進去,如果工作出現問題就呼叫 reject
}, delayTime);
});
}
// 語法糖,函式前面加 async, async / await 需要一起使用
async function test3() {
// await 後面接函式. 上面的 resolve 會直接由 result 接收
let result = await delayedAdd2(4, 2, 2000);
console.log(result);
}

需求 需要運行兩次的 promise 然後將結果相乘

1
2
3
4
5
6
7
8
async function test3a() {
// await 後面接函式. 上面的 resolve 會直接由 result 接收
let result1 = await delayedAdd2(4, 2, 2000);
let result2 = await delayedAdd2(3, 1, 3000);
let answer = result1 * result2;
console.log(answer);
}
test3a();

參考 1 彭彭的課程
參考 2 code