async函數學習筆記
含義
async函數是什麽?一句話,它就是Generator函數的語法糖。
const fs = require(‘fs‘) const readFile = function(fileName){ return new Promise(function(resolve,reject){ fs.readFile(fileName,function(error,data){ if(error) return reject(error); resolve(data); }) }) } const gen = function*(){ const f1 = yield readFile(‘/etc/fstab‘); const f2 = yield readFile(‘/etc/shells‘); console.log(f1.toString()); console.log(f2.toString()); } //上面代碼的函數gen可以寫成async函數,就是下面這樣。 const asyncReadFile = async function(){ const f1 = await readFile(‘/etc/fstab‘); cosnt f2 = await readFile(‘/etc/shells‘); console.log(f1.toString()); console.log(f2.toString()); }
一比較就會發現,async函數就是將Generator函數的星號替換成async,將yield替換成await,僅此而已。
async函數對Generator函數的改進,體現在以下四點。
(1)內置執行器
Generator函數的執行必須靠執行器,所以才有了co模塊,而async函數自帶執行器。也就是說,async函數的執行,與普通函數一模一樣,只要一行。
asyncReadFile()
上面的代碼調用了asyncReadFile函數,然後它就會自動執行,輸出最後結果。這完全不像Generator函數,需要調用next方法,或則用co模塊,才能真正執行,得到最後結果。
(2)更好的語義
async和await,比起星號和yield,語義更清楚了。async表示函數裏有異步操作,await表示緊跟在後面的表達式需要等待結果。
(3)更廣的適用性
co模塊約定,yield命令後面只能是Thunk函數或Promise對象,而async函數的await命令後面,可以是Promise對象和原始類型的值(數值、字符串和布爾值,但這時等同於同步操作)。
(4)返回值是Promise
async函數的返回值是Promise對象,這比Generator函數的返回值Iterator對象方便多了。你可以用then方法指定下一步的操作。
進一步說,async函數完全可以看作多個異步操作,包裝成的一個Promise對象,而await命令就是內部then命令的語法糖。
基本用法
async函數返回一個Promise對象,可以使用then方法添加回調函數。當函數執行的時候,一旦遇到await就會先返回,等到異步操作完成,再接著執行函數體內後面的語句。
下面是一個例子。
async function getStockPriceByName(name){ const symbol = await getStockSymbol(name); const stockPrice = await getStockPrice(symbol); return stockPrice; } getStockPriceByName(‘goog‘).then(function(result){ console.log(result); })
上面代碼是一個獲取股票報價的函數,函數前面的async關鍵字,表明該函數內部有異步操作。調用該函數時,會立即返回一個Promise對象。
下面是另一個例子,指定多少毫秒後輸出一個值。
function timeout(ms){
return new Promise((resolve)=>{
setTimeout(resolve,ms);
})
}
async function asyncPrint(value,ms){
await timeout(ms);
console.log(value)
}
asyncPrint(‘hello world‘,50);
由於async函數返回的是Promise對象,可以作為await命令的參數。所以,上面的例子也可以寫成下面的形式。
async function timeout(ms){
await new Promise((resolve)=>{
setTimeout(resolve,ms);
})
}
async function asyncPrint(value, ms) {
await timeout(ms);
console.log(value);
}
asyncPrint(‘hello world‘, 5000);
async函數有多種使用形式。
//函數聲明
async function foo(){}
//函數表達式
const foo = async function(){};
//對象的方法
let obj = {async foo(){}};
obj.foo().then(...)
//Class的方法
class Storage{
constructor(){
this.cachePromise = caches.open(‘avatars‘);
}
async getAvatar(name){
const cache = await this.cachePromise;
return cache.match(`/avatars/${name}.jpg`);
}
}
const storage = new Storage();
storage.getAvatar(‘jake‘).then(...);
//箭頭函數
const foo = async()=>{};
返回Promise對象
async函數返回一個Promise對象。
async函數內部return語句返回的值,會成為then方法回調函數的參數。
async function f(){
return ‘hello world‘;
}
f().then(v=>console.log(v))//‘hello world‘
上面代碼中,函數f內部return命令返回的值,會被then方法回調函數接收到。
async函數內部拋出錯誤,會導致返回Promise對象變為reject狀態。拋出的錯誤對象會被catch方法回調函數接收到。
async function f(){
throw new Error(‘出錯了‘);
}
f().then(
v=>console.log(v),
e=>console.log(e)
)
//Error;出錯了
Promise對象的狀態變化
async函數返回的Promise對象,必須等到內部所有await命令後面的Promise對象執行完,才會發生狀態改變,除非遇到return語句或則拋出錯誤。也就是說,只有async函數內部的異步操作執行完,才會執行then方法指定的回調函數。
async function getTitle(url) {
let response = await fetch(url);
let html = await response.text();
return html.match(/<title>([\s\S]+)<\/title>/i)[1];
}
getTitle(‘https://tc39.github.io/ecma262/‘).then(console.log)
上面代碼中,函數getTitle內部有三個操作:抓取網頁、取出文本、匹配頁面標題。只有這三個操作全部完成,才會執行then方法裏面的console.log()
await命令
正常情況下,await命令後面是一個Promise對象,返回該對象的結果。如果不是Promsie對象,就直接返回對應的值。
async function f(){
//等同於123;
return await 123;
}
f().then(v=>console.log(v))
上面代碼中,await命令的參數是數值123,這時等同於return 123.
另一種情況是,await命令後面是一個thenable對象(即定義then方法的對象),那麽await會將其等同於Promise對象。
class Sleep{
constructor(timeout){
this.timeout = timeout;
}
then(resolve,reject){
const startTime = Date.now();
setTimeout(
()=>resolve(Date.now()-startTime)
this.timeout
)
}
}
(async()=>{
const actualTime = await new Sleep(1000);
console.log(actualTime);
})();
上面代碼中,await命令後面是一個Sleep對象的實例。這個實例不是Promise對象,但是因為定義了then方法,await會將其視為Promise處理。
await命令後面的Promise對象如果變為reject狀態,則reject的參數會被catch方法的回調函數接收到。
async function f(){
await Promise.reject(‘出錯了‘);
}
f().then(v=>console.log(v)).catch(e=>console.log(e));
註意,上面代碼中,await語句前面沒有return,但是reject方法的參數依然傳入到catch方法的回調函數。這裏如果是await前面加上return,效果是一樣的。
任何一個await語句後面的Promise對象變為reject狀態,那麽整個async函數都會中斷執行。
有時,我們希望即使前一個異步操作失敗,也不要中斷後面的異步操作。這時可以將第一個await放在try...catch結構裏面,這樣不管這個異步操作是否成功,第二個await都會執行。
async function f(){
try{
await Promise.reject(‘出錯了‘);
}catch(e){
}
return await Promise.resolve(‘hello world‘);
}
f().then(v=>console.log(v))//hello world
另一種方法是await後面的Promise對象再跟一個catch方法,處理前面可能出現的錯誤。
async function f(){
await Promise.reject(‘出錯了‘)
.catch(e=>console.log(e));
return await Promise.resolve(‘hello world‘);
}
f().then(v=>console.log(v))
//出錯了
//hello world
錯誤處理
如果await後面的異步操作出錯,那麽等同於async函數返回的Promise對象被reject。防止出錯的方法,也是將其放在try...catch代碼塊之中。
async function f(){
try{
await new Promise(function(resolve,reject){
throw new Error(‘出錯了‘)
});
}catch(e){}
return await(‘hello world‘);
}
如果有多個await命令,可以統一放在try...catch結構中。
async function main(){
try{
const val1 = await firstStep();
const val2 = await secondStep(val1);
const val3 = await thirdStep(val1,val2);
console.log(‘Final‘,val3);
}
catch(err){
console.error(err);
}
}
下面的例子使用try...catch結構,實現多次重復嘗試。
const superagent = require(‘superagent‘);
const NUM_RETRIES = 3;
async function test(){
let i;
for(i=0;i<NUM_RETRIES;i++){
await superagent.get(‘http://goole.com/this-throws-an-error‘);
break;
}catch(err){}
console.log(i);//3
}
test();
上面代碼中,如果await操作成功,就會使用break語句退出循環;如果失敗,會被catch語句捕捉,然後進入下一輪循環。
來源:https://segmentfault.com/a/1190000017469670
async函數學習筆記