Zi 字媒體
2017-07-25T20:27:27+00:00
年前有個朋友面試,遇到了同步和非同步的問題。這幾年 JavaScript 也從 callback,慢慢演進到許多非同步的解法。
之前有提過 JavaScript 是一門有點 FP(Functional Progrmming)的語言,而 FP 把函式當成 first class(一等公民),所有的 function 都可以當成一種物件、當成一種參數來傳遞。像是你可以寫出這樣子的程式碼:
const onSuccess = (res) => {
if (res.result === 0) {
console.log('登入成功');
} else {
console.log('登入失敗');
}
};
login('username', 'password', onSuccess);
}
但你可能會比較習慣 callback 的形式,直接把那個 function 放在參數裡面,不另行宣告:
login('username', 'password', (res) => {
if (res.result === 0) {
console.log('登入成功');
} else {
console.log('登入失敗');
}
});
callback 是個很棒的寫法,可以很容易地看出哪個 function 執行完之後要做什麼事情。不過 callback 一多,縮排就會很醜:
login('username', 'password', (res) => {
if (res.result === 0) {
getPosts(res.uid, (posts) => {
if (posts && posts.length) {
console.log('你的文章有 ' + posts.length + ' 篇');
} else {
console.log('沒有文章');
}
});
} else {
console.log('登入失敗');
}
});
然後再多幾個 callback 就會變成龜派氣功了。這就是俗稱的 Callback Hell。
這裡(callbackhell.com) 有幾個做法教你如何避免 Callback Hell,像是宣告好函式然後拆開使用之類的。不過接下來要講的 Promise,才是 JS 試圖解決 Callback Hell 的方法。
Promise
有了 Promise 以後,你就可以把剛剛那段 code 改寫成這樣:
loginAsync('username', 'password')
.then((res) => {
if (res.result === 0)
return getPostAsync(res.uid);
return console.log('登入錯誤');
})
.then((posts) => {
if (posts && posts.length) {
console.log('你的文章有 ' + posts.length + ' 篇');
} else {
console.log('沒有文章');
}
});
可以像發動遊戲王卡的陷阱卡一樣一步一步串(chain)起來,只要用一個 Promise 把原先的動作包起來就好了。
而寫一個簡單的 Promise 可以這樣做:
const logAsync = (message, time) => {
return new Promise((resolve, reject) => {
if (message && time) {
setTimeout(() => {
console.log(message);
resolve()
}, time);
} else {
reject();
}
});
};
主要是回傳一個 Promise,Promise 裡面放著要執行的 function,然後成功的話呼叫 resolve 方法、失敗的話呼叫 reject 方法。這樣就可以把方法 chain 起來:
logAsync('這個訊息過一秒才會出現', 1000)
.then(() => {
return logAsync('這個訊息再過 1.5 秒才會出現', 1500);
})
.then(() => {
return logAsync('這個訊息再過 2 秒才會出現', 2000);
});
前往 此篇文章完整版 或到 CodePen 上查看程式碼
原本就存在的 function 可以以類似的方法用 Promise 包起來,這樣就能用 then 的方式連續呼叫多個方法了。
Promise 其實還有不少用法,之後有機會再開番外篇來介紹。這篇提 Promise 主要是為了後面的 async/await 鋪路,這才是 JS 非同步潮的地方。
你可能還聽過 generator,但現在其實很少用到這東西了。建議你學 async/await 就好了。
Async/await
async function 是不管怎樣都會回傳 Promise 的函式。例如:
const foo = async () => {
return 1;
}
foo().then((res) => {
console.log(res);
});
雖然我們的 foo 回傳的不是一個 Promise,但因為它是 async function 的關係,JS 會自動把它包成 Promise,所以可以使用 then,結果會得到 1。
而 await 則是可以等 Promise 執行完再執行下一行:
const demo = async () => {
await logAsync('1 秒後會出現這句', 1000);
await logAsync('再 1.5 秒後會出現這句', 1500);
await logAsync('再 2 秒後會出現這句', 2000);
};
demo();
不過 await 必須在 async function 裡面才能使用。
前往 此篇文章完整版 或到 CodePen 上查看程式碼
而且 await 也能夠把 Promise 回傳的值接起來,通常我們在呼叫 API(例如執行 fetch、axios)的時候就很好用:
(async () => {
const res = await fetch('API_URL');
const data = await res.text();
console.log(data);
})();
搭配 axios 更可以這樣使用:
((async () => {
const { data } = await axios.get('API_URL');
console.log(data);
})();
前往 此篇文章完整版 或到 CodePen 上查看程式碼
結語
使用 async/await 呼叫 API 或是其他非同步方法,不但可以避免 Callback Hell,比起 Promise 更增加了程式可讀性(這點見仁見智就是了),是個我覺得寫 JS 的朋友都應該會的東西。
最後,我覺得如果第一次碰 async/await,還是多看幾篇文章,看看不同人的觀點。我第一次碰這東西也是寫的霧煞煞,所以推薦幾篇文章:
鐵人賽:JavaScript Await 與 Async
告別 JavaScript 的 Promise!迎接 Async/Await 的到來
How To Master Async/Await With This Real World Example
第一篇先到這邊,下一篇來講運算子、選擇結構和函式。
我要學會 JS 目錄
我要學會 JS(一):JavaScript 簡介
我要學會 JS(二):基本運算與結構
我要學會 JS(三):callback、Promise 和 async/await 那些事兒
待續...
寫了
5860316篇文章,獲得
23313次喜歡