紀錄一下 javascript 中非同步的寫法範例
非同步範例
01 Promise
使用 new Promise(resolve,reject)
去模擬三個事件的執行順序,可以想見若未來還有其他事件要增加,最終的寫法肯定會越來越像氣功波,且難以維護
在事件內返回的是一個 Promise
,以便讓後續的 then()
語法可以接續下去,setTimeout()
是用來模擬 ajax 或是其他需要執行較長時間的程序,在程序結束完畢之後,透過resolve()
改變 Promise 的狀態為執行成功,這樣子在外面的執行事件偵測到就會往下執行then()
區塊
假設第一個事件是透過 ajax 取得後端資料,取得資料後需要再後續步驟接續處理資料,可以將資料傳入給resolve()
函式,這樣子在後續的then()
就可以作為輸入參數來使用
// 事件範例
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
事件方法與先前使用Promise
並無不同,但是在執行事件的方法中,需要宣告為async
表示此方法為一個非同步方法,並於該方法之內,將需要等候執行完成的方法前端加上await
表示此方法需要等候執行結束才可以往下繼續執行,使用語法糖來改寫Promise
的最大好處是:程式碼階層數量不再受到事件數量影響,便於閱讀也容易維護、撰寫
// 以 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
若要加上錯誤處理,則是添加上reject()
區段,並於執行事件的方法中加上catch()
來捕捉例外
jquery ajax API 返回的就是 promise,不需要再自己包一個
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
若改寫成 async / await
,Promise
發生錯誤時會拋出reject()
的異常,因此在執行事件中需要透過try...catch...
來處理
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 了,如果要用來控制程式流程,可以像下面這樣寫
{
// 只是拿來做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();
}