js 非同步事件練習

紀錄一下 javascript 中非同步的寫法範例

非同步範例

01 Promise

Sample Page 01

使用 new Promise(resolve,reject) 去模擬三個事件的執行順序,可以想見若未來還有其他事件要增加,最終的寫法肯定會越來越像氣功波,且難以維護

在事件內返回的是一個 Promise,以便讓後續的 then()語法可以接續下去,setTimeout()是用來模擬 ajax 或是其他需要執行較長時間的程序,在程序結束完畢之後,透過resolve()改變 Promise 的狀態為執行成功,這樣子在外面的執行事件偵測到就會往下執行then()區塊

假設第一個事件是透過 ajax 取得後端資料,取得資料後需要再後續步驟接續處理資料,可以將資料傳入給resolve()函式,這樣子在後續的then()就可以作為輸入參數來使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 事件範例
work01(ms) {
return new Promise((resolve, reject) => {
setTimeout(ms => {
let time = new Date();
resolve({ title, time });
}, ms);
});
},

// 執行事件範例
this.work01(3000).then(info => {
this.showInfo(info);
this.work02(2000).then(info => {
this.showInfo(info);
this.work03(1000).then(info => {
this.showInfo(info);
});
});
});

02 async / await

Sample Page 02

事件方法與先前使用Promise並無不同,但是在執行事件的方法中,需要宣告為async表示此方法為一個非同步方法,並於該方法之內,將需要等候執行完成的方法前端加上await表示此方法需要等候執行結束才可以往下繼續執行,使用語法糖來改寫Promise的最大好處是:程式碼階層數量不再受到事件數量影響,便於閱讀也容易維護、撰寫

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 以 async / await 改寫執行事件
async doJob() {
const r1 = await this.work01(3000);
this.showInfo(r1);
const r2 = await this.work02(2000);
this.showInfo(r2);
const r3 = await this.work03(1000);
this.showInfo(r3);
}
// or 使用inline method 凸顯程式意圖
async doJob() {
this.showInfo(await this.work01(3000));
this.showInfo(await this.work02(2000));
this.showInfo(await this.work03(1000));
}

03 Promise catch error

Sample Page 03

若要加上錯誤處理,則是添加上reject()區段,並於執行事件的方法中加上catch()來捕捉例外

jquery ajax API 返回的就是 promise,不需要再自己包一個

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
28
29
30
31
32
33
new Vue({
el: "#app",
data: {
infos: [],
},

methods: {
ajaxPromise(params) {
return $.ajax({
type: params.type || "get",
async: params.async || true,
url: params.url,
data: params.data || "",
});
},
setData(data) {
this.infos = data;
},

async doJob() {
this.ajaxPromise({ url: "dataNotExist.json" })
.then((data) => {
this.setData(data);
})
.catch((err) => {
console.log(err);
});
},
},
mounted() {
this.doJob();
},
});

04 async / await catch error

Sample Page 04

若改寫成 async / awaitPromise 發生錯誤時會拋出reject()的異常,因此在執行事件中需要透過try...catch...來處理

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
28
29
30
31
32
new Vue({
el: "#app",
data: {
infos: [],
},

methods: {
ajaxPromise(params) {
return $.ajax({
type: params.type || "get",
async: params.async || true,
url: params.url,
data: params.data || "",
});
},
setData(data) {
this.infos = data;
},

async doJob() {
try {
let data = await this.ajaxPromise({ url: "dataNotExist.json" });
this.setData(data);
} catch (error) {
console.log(error);
}
},
},
mounted() {
this.doJob();
},
});

實際經驗

因為對非同步真的不熟,在實務上也犯了將$.ajax再包一層 promise 卻發現無法正確工作,查了很久才發現這件事情,特別紀錄一下

使用$.ajax語法就已經是回傳非同步的 Promise 了,如果要用來控制程式流程,可以像下面這樣寫

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
28
29
30
31
32
33
34
{
// 只是拿來做console的ID用
// ref:https://stackoverflow.com/questions/1349404/generate-random-string-characters-in-javascript
function makeId(length) {
var result = "";
var characters =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
var charactersLength = characters.length;
for (var i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}

// 記得要return,才會將promise回傳出去
function test(params) {
let id = makeId(3);
console.log(`${id} start`);
return $.ajax({
url: "http://localhost:40001/mock/8/Common/GetZipCode",
method: "POST",
}).then(() => {
console.log(`${id} end`);
});
}

// 宣告為async,並於回傳的promise方法之前加上 await 修飾詞
async function init() {
await test();
await test();
}

init();
}