本文轉自我的博客閱讀原文。
整體感知
async/await提供給我們一種同步的方式來編寫異步代碼。如果去掉await關鍵字,下面這段異步代碼就跟我們常見的同步代碼別無二致了。
// 定義一個返回Promise對象的函數
function fn() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(30)
}, 1000)
})
}
// 然后利用async/await來完成代碼
const foo = async() => {
const t = await fn()
console.log(t)
console.log('next code')
}
foo()
// 30
// next code
更進一步
前世————Generator函數
async/await的前世是ES6提供的Generator函數。那么下面來瞧一瞧Generator函數是個什么鬼。
function* gen(x) {
var y = yield x + 2
return y
}
var g = gen(1)
g.next() // { value: 3, done: false }
g.next() // { value: undefined, done: true }
簡單粗暴地說,Generator函數就是一種可以暫停執行的函數。我們在調用Generator函數時,會返回一個內部指針(即遍歷器)。繼續調用這個內部指針的next方法才會執行Generator函數體中的語句,然后遇到以yield關鍵字則會暫停執行,知道再一次調用next方法。
調用遍歷器的next方法會返回形如{value: xx, done: bool}
的對象。value接收yield之后的值,done表示函數是否執行完畢。
遍歷器的next方法可以接收外部傳入的參數,該參數將會被當作上一個yield的值參與運算。
下面看看如何用Generator函數來實現異步操作:
var fetch = require('node-fetch')
function* gen(){
var url = 'https://api.github.com/users/github'
var result = yield fetch(url)
console.log(result.bio)
}
/* 執行這段代碼如下 */
var g = gen();
// result得到的是一個Promise對象
var result = g.next();
result.value.then(function(data){
return data.json();
}).then(function(data){
// 得到異步返回的數據之后調用下一個next,并且將數據傳進next方法
g.next(data);
});
async/await
為什么要用async
async函數是什么?一句話,它就是Generator函數的語法糖。
上面我們利用Generator函數封裝了異步操作,但是那種寫法比較別扭。首先,我們要將異步操作用Promise封裝起來,其次,當異步完成之后進行下一個操作時,需要手動地調用next方法。
async函數的出現,就彌補了我們提到的不足:
async function getStockPriceByName(name) {
// 正常情況下,await命令后面是一個 Promise 對象。如果不是,會被轉成一個立即resolve的 Promise 對象。
var symbol = await getStockSymbol(name)
var stockPrice = await getStockPrice(symbol)
return stockPrice
}
// async函數返回一個Promise對象。async函數內部return語句返回的值,會成為then方法回調函數的參數。
getStockPriceByName('goog').then(function (result) {
console.log(result)
})
不讓前面的錯誤影響后面的操作
有時,我們希望即使前一個異步操作失敗,也不要中斷后面的異步操作。這時可以將第一個await放在try...catch結構里面,這樣不管這個異步操作是否成功,第二個await都會執行。
async function f() {
try {
await Promise.reject('出錯了')
} catch(e) {
}
// 即使await后面的語句報錯,下面這個await還是會執行
return await Promise.resolve('hello world')
}
f()
.then(v => console.log(v))
// hello world
錯誤處理
await命令后面的Promise對象,運行結果可能是rejected,所以最好把await命令放在try...catch代碼塊中。如果await后面的異步操作出錯,那么等同于async函數返回的 Promise 對象被reject。
async function myFunction() {
try {
await somethingThatReturnsAPromise()
} catch (err) {
console.log(err)
}
}
// 另一種寫法
async function myFunction() {
await somethingThatReturnsAPromise()
.catch(function (err) {
console.log(err)
});
}
并發的異步
上面的寫法都是處理繼發狀況,下面來說說并發異步的處理:
// 寫法一
let [foo, bar] = await Promise.all([getFoo(), getBar()])
// 寫法二
let fooPromise = getFoo()
let barPromise = getBar()
let foo = await fooPromise
let bar = await barPromise