您好,登錄后才能下訂單哦!
這篇文章給大家介紹怎么在JavaScript中使用async函數,內容非常詳細,感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。
一、終極解決
異步操作是 JavaScript 編程的麻煩事,麻煩到一直有人提出各種各樣的方案,試圖解決這個問題。
從最早的回調函數,到 Promise 對象,再到 Generator 函數,每次都有所改進,但又讓人覺得不徹底。它們都有額外的復雜性,都需要理解抽象的底層運行機制。
異步I/O不就是讀取一個文件嗎,干嘛要搞得這么復雜?異步編程的最高境界,就是根本不用關心它是不是異步。
async 函數就是隧道盡頭的亮光,很多人認為它是異步操作的終極解決方案。
二、async 函數是什么?
一句話,async 函數就是 Generator 函數的語法糖。
前文有一個 Generator 函數,依次讀取兩個文件。
var fs = require('fs'); var readFile = function (fileName){ return new Promise(function (resolve, reject){ fs.readFile(fileName, function(error, data){ if (error) reject(error); resolve(data); }); }); }; var gen = function* (){ var f1 = yield readFile('/etc/fstab'); var f2 = yield readFile('/etc/shells'); console.log(f1.toString()); console.log(f2.toString()); };
寫成 async 函數,就是下面這樣。
var asyncReadFile = async function (){ var f1 = await readFile('/etc/fstab'); var 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 函數的執行,與普通函數一模一樣,只要一行。
var result = asyncReadFile();
(2)更好的語義。 async 和 await,比起星號和 yield,語義更清楚了。async 表示函數里有異步操作,await 表示緊跟在后面的表達式需要等待結果。
(3)更廣的適用性。 co 函數庫約定,yield 命令后面只能是 Thunk 函數或 Promise 對象,而 async 函數的 await 命令后面,可以跟 Promise 對象和原始類型的值(數值、字符串和布爾值,但這時等同于同步操作)。
async 函數的實現,就是將 Generator 函數和自動執行器,包裝在一個函數里。
async function fn(args){ // ... } // 等同于 function fn(args){ return spawn(function*() { // ... }); }
所有的 async 函數都可以寫成上面的第二種形式,其中的 spawn 函數就是自動執行器。
下面給出 spawn 函數的實現,基本就是前文自動執行器的翻版。
function spawn(genF) { return new Promise(function(resolve, reject) { var gen = genF(); function step(nextF) { try { var 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); }); }); }
async 函數是非常新的語法功能,新到都不屬于 ES6,而是屬于 ES7。目前,它仍處于提案階段,但是轉碼器 Babel 和 regenerator 都已經支持,轉碼后就能使用。
同 Generator 函數一樣,async 函數返回一個 Promise 對象,可以使用 then 方法添加回調函數。當函數執行的時候,一旦遇到 await 就會先返回,等到觸發的異步操作完成,再接著執行函數體內后面的語句。
下面是一個例子。
async function getStockPriceByName(name) { var symbol = await getStockSymbol(name); var 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);
上面代碼指定50毫秒以后,輸出"hello world"。
await 命令后面的 Promise 對象,運行結果可能是 rejected,所以最好把 await 命令放在 try...catch 代碼塊中。
async function myFunction() { try { await somethingThatReturnsAPromise(); } catch (err) { console.log(err); } } // 另一種寫法 async function myFunction() { await somethingThatReturnsAPromise().catch(function (err){ console.log(err); }); }
await 命令只能用在 async 函數之中,如果用在普通函數,就會報錯。
async function dbFuc(db) { let docs = [{}, {}, {}]; // 報錯 docs.forEach(function (doc) { await db.post(doc); }); }
上面代碼會報錯,因為 await 用在普通函數之中了。但是,如果將 forEach 方法的參數改成 async 函數,也有問題。
async function dbFuc(db) { let docs = [{}, {}, {}]; // 可能得到錯誤結果 docs.forEach(async function (doc) { await db.post(doc); }); }
上面代碼可能不會正常工作,原因是這時三個 db.post 操作將是并發執行,也就是同時執行,而不是繼發執行。正確的寫法是采用 for 循環。
async function dbFuc(db) { let docs = [{}, {}, {}]; for (let doc of docs) { await db.post(doc); } }
如果確實希望多個請求并發執行,可以使用 Promise.all 方法。
async function dbFuc(db) { let docs = [{}, {}, {}]; let promises = docs.map((doc) => db.post(doc)); let results = await Promise.all(promises); console.log(results); } // 或者使用下面的寫法 async function dbFuc(db) { let docs = [{}, {}, {}]; let promises = docs.map((doc) => db.post(doc)); let results = []; for (let promise of promises) { results.push(await promise); } console.log(results); }
關于怎么在JavaScript中使用async函數就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。