譯者按: 在 Async/Await替代Promise的6個理由 中,我們比較了兩種不同的異步程式設計方法:Async/Await和Promise,這篇部落格将通過示例代碼介紹Async/Await是如何簡化JavaScript代碼的。
為了保證可讀性,本文采用意譯而非直譯。另外,本文版權歸原作者所有,翻譯僅用于學習。
Async/Await是JavaScript的ES7新特性,來源于.NET和C#。它可以不用回調函數,像同步代碼那些編寫異步代碼。這篇部落格将通過一些代碼示例,來說明****Async/Await****如何簡化JavaScript代碼。
1. 去除回調函數
運作本文的示例代碼,并不需要額外的函數庫。對于最新版的主流浏覽器中,例如Chrome,Firefox, Safari以及Edge,它們都支援Async/Await文法。另外,Node.js 7.6+也支援了Async/Await文法。
我們編寫了一些簡單的API接口,用于模拟異步操作。這些接口都傳回Promise,并在200ms後****resolve****一些資料。
class Api {
constructor () {
this.user = { id: 1, name: 'test' }
this.friends = [ this.user, this.user, this.user ]
this.photo = 'not a real photo'
}
getUser () {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(this.user), 200)
})
}
getFriends (userId) {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(this.friends.slice()), 200)
})
}
getPhoto (userId) {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(this.photo), 200)
})
}
throwError () {
return new Promise((resolve, reject) => {
setTimeout(() => reject(new Error('Intentional Error')), 200)
})
}
}
嵌套Promise
function callbackHell () {
const api = new Api()
let user, friends
api.getUser().then(function (returnedUser) {
user = returnedUser
api.getFriends(user.id).then(function (returnedFriends) {
friends = returnedFriends
api.getPhoto(user.id).then(function (photo) {
console.log('callbackHell', { user, friends, photo })
})
})
})
}
曾經使用Promise編寫回調函數的開發者一定不會陌生,這樣一層層的嵌套代碼通常是這樣結尾的:
})
})
})
}
在回調函數中調用回調函數,一層層地嵌套,這就是所謂的“回調地獄”。在真實的代碼中,這樣的情況并不少見,通常更為複雜。
鍊式Promise
function promiseChain () {
const api = new Api()
let user, friends
api.getUser()
.then((returnedUser) => {
user = returnedUser
return api.getFriends(user.id)
})
.then((returnedFriends) => {
friends = returnedFriends
return api.getPhoto(user.id)
})
.then((photo) => {
console.log('promiseChain', { user, friends, photo })
})
}
Promise的最佳特性之一,就是可以在then回調函數中,return一個新的Promise,這樣就可以将這些Promise連結起來,隻有一層嵌套。鍊式Promise比嵌套Promise簡單很多,但是還是很多備援。
Async/Await
不使用回調函數可以嗎?當然可以!使用Async/Await的話,7行代碼就可以搞定。
async function asyncAwaitIsYourNewBestFriend () {
const api = new Api()
const user = await api.getUser()
const friends = await api.getFriends(user.id)
const photo = await api.getPhoto(user.id)
console.log('asyncAwaitIsYourNewBestFriend', { user, friends, photo })
}
使用await關鍵詞時,指派操作将等到異步操作結束時才進行。這樣,看起來與同步代碼無異,實際執行事實上是異步的。
2. 簡化循環
Async/Await可以讓一些複雜操作,比如循環變得簡單。例如,當我們需要擷取某個user的所有friends的friends清單,應該怎樣操作呢?
使用Promise
function promiseLoops () {
const api = new Api()
api.getUser()
.then((user) => {
return api.getFriends(user.id)
})
.then((returnedFriends) => {
const getFriendsOfFriends = (friends) => {
if (friends.length > 0) {
let friend = friends.pop()
return api.getFriends(friend.id)
.then((moreFriends) => {
console.log('promiseLoops', moreFriends)
return getFriendsOfFriends(friends)
})
}
}
return getFriendsOfFriends(returnedFriends)
})
}
我們使用了遞歸函數getFriendsOfFriends來擷取friends-of-friends,知道friends數組為空。如此簡單的任務,這樣寫顯然過于複雜了。
使用Promise.all()來實作的話,則并非循環,而是并發執行。
使用Async/Await
This could be so much easier.
async function asyncAwaitLoops () {
const api = new Api()
const user = await api.getUser()
const friends = await api.getFriends(user.id)
for (let friend of friends) {
let moreFriends = await api.getFriends(friend.id)
console.log('asyncAwaitLoops', moreFriends)
}
}
這時,可以直接使用for循環來實作,非常簡單。
3. 簡化并發
使用循環逐個擷取friends-of-friends顯然太慢,采用并發方式更為簡單。
async function asyncAwaitLoopsParallel () {
const api = new Api()
const user = await api.getUser()
const friends = await api.getFriends(user.id)
const friendPromises = friends.map(friend => api.getFriends(friend.id))
const moreFriends = await Promise.all(friendPromises)
console.log('asyncAwaitLoopsParallel', moreFriends)
}
為了實作并發,隻需要将Promise數組作為Promise.all()的參數即可。這樣,隻需要await一個Promise,而這個Promise會在所有并發操作結束時resolve。
4. 簡化錯誤處理
使用回調函數處理Promise錯誤
function callbackErrorHell () {
const api = new Api()
let user, friends
api.getUser().then(function (returnedUser) {
user = returnedUser
api.getFriends(user.id).then(function (returnedFriends) {
friends = returnedFriends
api.throwError().then(function () {
console.log('Error was not thrown')
api.getPhoto(user.id).then(function (photo) {
console.log('callbackErrorHell', { user, friends, photo })
}, function (err) {
console.error(err)
})
}, function (err) {
console.error(err)
})
}, function (err) {
console.error(err)
})
}, function (err) {
console.error(err)
})
}
這樣做非常糟糕,代碼非常備援,可讀性也很差。
使用catch方法處理Promise錯誤
function callbackErrorPromiseChain () {
const api = new Api()
let user, friends
api.getUser()
.then((returnedUser) => {
user = returnedUser
return api.getFriends(user.id)
})
.then((returnedFriends) => {
friends = returnedFriends
return api.throwError()
})
.then(() => {
console.log('Error was not thrown')
return api.getPhoto(user.id)
})
.then((photo) => {
console.log('callbackErrorPromiseChain', { user, friends, photo })
})
.catch((err) => {
console.error(err)
})
}
這樣處理好多了,僅僅需要在Promise鍊的最後,使用catch方法處理所有錯誤。
使用Try/Catch處理Async/Await錯誤
async function aysncAwaitTryCatch () {
try {
const api = new Api()
const user = await api.getUser()
const friends = await api.getFriends(user.id)
await api.throwError()
console.log('Error was not thrown')
const photo = await api.getPhoto(user.id)
console.log('async/await', { user, friends, photo })
} catch (err) {
console.error(err)
}
}
對于Async/Await代碼,使用Try/Catch即可處理,和同步代碼一樣,更加簡單。
如何你需要監控線上JavaScript代碼的錯誤時,可以免費使用
的實時錯誤監控服務,隻需要一行代碼就可以搞定!
5. 簡化代碼組織
使用async關鍵詞定義的函數都會傳回Promise,這樣可以更友善地組織代碼。
例如,在之前的示例中,我們可以将擷取的user資訊return,而不是直接列印;然後,我們可以通過傳回的Promise來擷取user資訊。
async function getUserInfo () {
const api = new Api()
const user = await api.getUser()
const friends = await api.getFriends(user.id)
const photo = await api.getPhoto(user.id)
return { user, friends, photo }
}
function promiseUserInfo () {
getUserInfo().then(({ user, friends, photo }) => {
console.log('promiseUserInfo', { user, friends, photo })
})
}
使用Async/Await文法,則更加簡單:
async function awaitUserInfo () {
const { user, friends, photo } = await getUserInfo()
console.log('awaitUserInfo', { user, friends, photo })
}
如何擷取多個user的資訊?
async function getLotsOfUserData () {
const users = []
while (users.length < 10) {
users.push(await getUserInfo())
}
console.log('getLotsOfUserData', users)
}
如何并發?如何處理錯誤?
async function getLotsOfUserDataFaster () {
try {
const userPromises = Array(10).fill(getUserInfo())
const users = await Promise.all(userPromises)
console.log('getLotsOfUserDataFaster', users)
} catch (err) {
console.error(err)
}
}

版權聲明:
轉載時請注明作者
以及本文位址:
https://blog.fundebug.com/2017/10/16/async-await-simplify-javascript/