ES2017
標准引入了 async
函數,使得異步操作變得更加方便,由於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', 50);
返回 Promise
對象
async function f() { return 'hello world'; } f().then(v => console.log(v)) // "hello world"
async
函數內部拋出錯誤,會導致返回的Promise
對象變為reject
狀態。拋出的錯誤對象會被catch
方法回調函數接收到
async function f() { throw new Error('出錯了'); } f().then( v => console.log('resolve', v), e => console.log('reject', e) ) //reject 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('http://localhost:8080/').then(console.log(123))
await
命令
正常情況下,await
命令后面是一個 Promise
對象,返回該對象的結果。如果不是 Promise
對象,就直接返回對應的值。
async function f() { // 等同於 // return 123; return await 123; } f().then(v => console.log(v)) // 123
任何一個await
語句后面的 Promise
對象變為reject
狀態,那么整個async
函數都會中斷執行。
async function f() { await Promise.reject('出錯了'); await Promise.resolve('hello world'); // 不會執行 }
如果希望即使前一個異步操作失敗,也不要中斷后面的異步操作。這時可以將第一個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
async
函數的實現原理
將 Generator 函數和自動執行器,包裝在一個函數里
async function fn(args) { // ... } // 等同於 function fn(args) { return spawn(function* () { // ... }); } function spawn(genF) { return new Promise(function(resolve, reject) { const gen = genF(); function step(nextF) { let next; try { next = nextF(); } catch(e) { return reject(e); } if(next.done) { return resolve(next.value); } Promise.resolve(next.value).then(function(v) { step(function() { return gen.next(v); }); }, function(e) { step(function() { return gen.throw(e); }); }); } step(function() { return gen.next(undefined); }); }); }