[JS] 同步非同步霧煞煞(下)-Promise,Async,Await

[JS] 同步非同步霧煞煞(下)-Promise,Async,Await

前言

接續上一篇同步非同步霧煞煞(上)-AJAX,這篇會介紹Callback hell, promise, Async, Await

Callback hell 回呼地獄

上一篇提到有非同步特性方法像是Ajax, setTimeout等,當我們使用這些有非同步特性方法,但又希望可以按照我們想要的順序來執行時,一種解決方式是用callback function

DEMO

setTimeout的延遲時間為隨機秒數,非同步的特性無法控制哪個setTimeout先執行,把setTimeout個別用 function包起來,且各個 function 都帶入一個 callback 參數。B為A的callback,C為B的,但是如果有太多函式要依序就會變成callback hell,導致程式碼過巢會很難閱讀。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function funcA(check){
setTimeout(function(){
console.log('A');
check('A');
}, Math.random() * 1000);
}

function funcB(check){
setTimeout(function(){
console.log('B');
check('B');
}, Math.random() * 1000);
}

function funcC(check){
setTimeout(function(){
console.log('C');
check('C');
}, Math.random() * 1000);
}

funcA(function(){
funcB(function(){
funcC(function(){
});
});
});

Promise

早期也有另一個方式是generator,但很少人這樣做。而現在最多人使用的則是Promise來解決常見的非同步問題:

  1. Callback hell
  2. 寫法不一致(promise用.then來回呼)
  3. 無法同時執行無法同時執行(jQuery有並行寫法但不直覺)

Promise 可能處於三種階段中任意階段:
Pending:正在進行中,還不知道是完成或失敗
Resolved(Fulfilled):表示成功,回傳結果
Rejected:表示失敗,回傳失敗原因

1
2
3
4
const newPromise = new Promise((resolve, reject) => {
resolve(someValue); // 成功
reject('失敗原因'); // 失敗
});

then()、catch()、finally()

在function使用promise功能,return一個 promise 物件即可,then()接收成功,catch()接收失敗,finally()非同步執行完畢(無論是否正確完成):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function promiseFunction(timeout) {
// console.log(1) 執行程序1
return new Promise((resolve, reject) => {
// console.log(2) 執行程序2
setTimeout(() => {
resolve('成功');
// console.log(4) 執行程序4
},timeout);
if (timeout === 0){
reject('失敗'); // reject(new Error('失敗'))
}
});
};

promiseFunction(1000).then((response) =>{
console.log('執行成功', response);
}).catch((error)=>{
console.log('執行失敗', error);
});

// console.log(3) 執行程序3

Promise Chain

要串連執行多個 promise 功能的話,則用then()來連結,若其中一個沒成功則會跳到失敗。

1
2
3
4
5
6
7
8
9
10
11
12
13
promiseFunction(1000).then((response) =>{
console.log('執行成功', response);
return promiseFunction(2000);
}).then((response) =>{
console.log('執行成功2', response);
return promiseFunction(3000);
}).then((response) =>{
console.log('執行成功3', response);
})
.catch((error)=>{
console.log('執行失敗', error);
});

用promise解決上面callback hell的demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function promiseFunction(func) {
return new Promise((resolve, reject) => {
func(function(){
resolve();
})
});
};

promiseFunction(funcA).then(()=>{
return promiseFunction(funcB);
}).then(()=>{
return promiseFunction(funcC);
}).catch((error)=>{
console.log('執行失敗', error);
}); //若其中一個沒成功則會跳到失敗

Promise.all()

如果funcA, funcB, funcC 的先後順序不重要,直到這三個函式都回覆成功或是其中一個跳失敗 才可以繼續後面行為,可以透過 Promise.all() 來做到:

1
2
3
4
Promise.all([promiseFunction(funcA), promiseFunction(funcB), promiseFunction(funcC)])
.then((response) =>{
console.log(response);
});

適合需要多支 API 要一起執行,並確保全部完成後才進行其他工作時。

Promise.race()

同時執行多支API,但僅會回傳最快回應的結果

1
2
3
4
Promise.race([promiseFunction(funcA), promiseFunction(funcB), promiseFunction(funcC)])
.then((response) =>{
console.log(response);
});

Async、Await

async await 是ES7的語法,搭配Promise來使用,寫法更精簡閱讀起來更簡單,像是在讀同步程式碼,兩者必須一起使用

使用 async function

定義一個非同步函式,讓這個函式本體是屬於非同步,但其內部以“同步的方式運行非同步”程式碼。

1
2
3
4
5
6
7
8
9
async function asyncFn() {
return data
}
console.log(asyncFn());
//可以得到與 Promise 結構相似的函式,該函式是以非同步的方式運行,無法直接使用 console.log 取得其值。

asyncFn().then(r => {
console.log(r)
});

await

可以暫停非同步函式的運行(中止 Promise 的運行),直到非同步進入 resolve 或 reject,當接收完回傳值後繼續非同步函式的運行。

1
2
3
4
5
6
7
8
9
10
11
12
13
function promiseFunction(func) {
return new Promise((resolve, reject) => {
func(function(){
resolve();
})
});
};

(async function () {
await promiseFunction(funcA);
await promiseFunction(funcB);
await promiseFunction(funcC);
})();

try…catch

用try和catch來處理成功和失敗

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function promiseFunction(func) {
return new Promise((resolve, reject) => {
func(function(){
reject('失敗');
})
});
};

(async function () {
try{
await promiseFunction(funcA);
await promiseFunction(funcB);
await promiseFunction(funcC);
}
catch(error){
console.log(error);
}
})();

回傳不同錯誤訊息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(async function () {
try{
await promiseFunction(funcA);
} catch(error){
console.log('A', error);
}
try{
await promiseFunction(funcB);
} catch(error){
console.log('B', error);
}
try{
await promiseFunction(funcC);
} catch(error){
console.log('C', error);
}
})();

搭配 Promise.all()

1
2
3
4
5
6
7
8
9
(async function () {
try{
const data = await Promise.all([promiseFunction(funcA), promiseFunction(funcB), promiseFunction(funcC)]);
console.log(data);
}
catch(error){
console.log(error);
}
})();

Fetch搭配Async、Await

fetch 也是基於 Promise 的 Web API,因此它也同樣能夠使用 async/await 來進行改寫,fetch 與一般 AJAX 套件比較不同之處是在 JSON 回傳後,必須在使用 json() 的方法將資料輸出成 JSON 格式(相關介紹可以參考:MDN Fetch)。

當使用 Promise then 時,則會使用 return 來呼叫 json() 方法(箭頭函式縮寫,所以省略了 return),

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const url = 'https://randomuser.me/api/';

fetch(url).then(response => response.json())
.then(data => console.log(data))
.catch((err) => {
console.log('錯誤:', err);

//async 改寫

const url = 'https://randomuser.me/api/';
(async function(){
try{
const response = await fetch(url);
const data = await response.json();
console.log(data);
}
catch(error){
console.log(error);
}
})();

[JS] 同步非同步霧煞煞(下)-Promise,Async,Await

https://kaiyuncheng.github.io/2020/11/11/promise/

Author

KaiYun Cheng

Posted on

2020-11-11

Updated on

2024-04-13

Licensed under

Comments

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×