寫好一個優秀的web應用關鍵之一就是可以在一個頁面上做許多AJAX請求
前言
我們可以從一個簡單的例子的每個解決方式來思考JavaScript異步編程的進步
爲了做到這些,我們可以來做一個簡單的任務,這個任務是完成下面這些流程:
- 驗證用戶的名稱和密碼
- 獲取應用中用戶的角色
- 打印用戶訪問應用的時間
回調地獄的方式
最古老的解決這些問題的方式是通過一層套一層的的回調。這在過去是解決簡單的異步任務的優雅方式,但是呢,由於回調地獄的原因,並不能進行拓展升級。
用於解決三個簡單問題的代碼如下
const verifyUser = function(username, password, callback){
dataBase.verifyUser(username, password, (error, userInfo) => {
if (error) {
callback(error)
}else{
dataBase.getRoles(username, (error, roles) => {
if (error){
callback(error)
}else {
dataBase.logAccess(username, (error) => {
if (error){
callback(error);
}else{
callback(null, userInfo, roles);
}
})
}
})
}
})
};
每個函數的調用都需要傳遞一個參數,這個參數也是一個函數,會接受前一個函數的返回值。
僅僅閱讀上面的句子都會使得太多人大腦麻木,而如果一個應用擁有數以百計的這種代碼的話,會對維護這些代碼的人造成極大的困擾,即使是這些人自己寫的這些代碼(不要高估明天的自己,明天的你不一定可以看懂今天的你寫的代碼)。
當你知道database.getRoles`也是一個嵌套回調的函數的時候,你會意識到這個例子變得更加複雜。
const getRoles = function (username, callback){
database.connect((connection) => {
connection.query('get roles sql', (result) => {
callback(null, result);
})
});
};
這些代碼的問題,除了代碼難以維護以外,還有就是違背DRY原則(Do not repeat yourself)。例如,異常處理在每個函數中都被重複的進行(if else)並且調用的callback也在每個嵌套的function中調用。
JavaScript中的Promises
Promise
是避免回調地獄的一個進步。此方法並沒有移除回調函數,但是將函數的調用連接起來並且簡化了代碼,使得代碼更易於閱讀。
使用了Promise
的代碼如下
const verifyUser = function(username, password) {
database.verifyUser(username, password)
.then(userInfo => dataBase.getRoles(userInfo))
.then(rolesInfo => dataBase.logAccess(rolesInfo))
.then(finalResult => {
//do whatever the 'callback' would do
})
.catch((err) => {
//do whatever the error handler needs
});
};
爲了做到這些,代碼中使用到的函數必須Promise化。我們看一下getRoles
如何更新的返回一個Promise對象的。
const getRoles = function (username){
return new Promise((resolve, reject) => {
database.connect((connection) => {
connection.query('get roles sql', (result) => {
resolve(result);
})
});
});
};
我們將此方法修改爲返回一個Promise
對象,此對象需要傳入兩個回調函數,並且在Promise對象中執行操作。現在,resolve
和reject
兩個回調函數會分別映射爲Promise.then
和Promise.catch
。
你可能意識到getRoles
方法的內部仍然是可能遭受地獄回調的,因爲database的方法並沒有返回一個Promise對象。如果database的連接方法也返回一個Promise對象,那麼getRoles
就會像下面這樣
const getRoles = new function (userInfo) {
return new Promise((resolve, reject) => {
database.connect()
.then((connection) => connection.query('get roles sql'))
.then((result) => resolve(result))
.catch(reject)
});
};
方式3:Async/Await
JavaScript默認是異步的。這也可能是爲什麼JavaScript花了很長時間才使得代碼看起來像同步的。但是,遲到了總比沒有強。地獄回調在引入了Promise對象之後改善了很多。但是我們仍然需要傳遞迴調函數給Promise對象.then
和.catch
。
Promise給JavaScript帶來了最酷之一的改變。ECMAScript 2017在Promise之上已async和await表達式的方式帶來了語法糖。這些使得我們可以基於Promise的代碼看起來不像是異步的一樣,並且也不會阻塞主線程。
代碼如下:
const verifyUser = async function(username, password){
try {
const userInfo = await dataBase.verifyUser(username, password);
const rolesInfo = await dataBase.getRoles(userInfo);
const logStatus = await dataBase.logAccess(userInfo);
return userInfo;
}catch (e){
//handle errors as needed
}
};
await promise
的操作僅僅允許在async
修飾的函數中使用,async
修飾的函數表示verifyUser必須定義爲異步函數
然而,這些簡單的改變就可以await
任何Promise
,並且不需要修改任何代碼。並且異步代碼可以想同步代碼一樣愉快的編碼了。
Async 一個等待已久的Promise的進步
異步函數是JavaScript中異步編程的下一個里程碑。他們會使得代碼更爲乾淨並且更爲簡單的維護。聲明一個函數爲async會確保函數返回的是一個Promise對象(即不需要單獨的定義返回的對象爲一個Promise
對象),所以你並不需要爲此再擔心了。
我們如今爲什麼需要使用JavaScript中的async
函數?
- 代碼會更爲整潔
- 異常處理更爲簡單,僅僅像在其他異步代碼使用
try/catch
即可 - 調試更爲簡單。在
.then
代碼塊設置斷點不會移動到下一個斷點,.then
只會執行同步代碼。但是調試async
函數就和調試同步代碼一樣
本文翻譯自https://blog.hellojs.org/asynchronous-javascript-from-callback-hell-to-async-and-await-9b9ceb63c8e8