js 中async
一、終極解決
異步操作是 JavaScript 編程的麻煩事,麻煩到一直有人提出各種各樣的方案,試圖解決這個(gè)問題。
從最早的回調(diào)函數(shù),到 Promise 對象,再到 Generator 函數(shù),每次都有所改進(jìn),但又讓人覺得不徹底。它們都有額外的復(fù)雜性,都需要理解抽象的底層運(yùn)行機(jī)制。
異步I/O不就是讀取一個(gè)文件嗎,干嘛要搞得這么復(fù)雜?異步編程的最高境界,就是根本不用關(guān)心它是不是異步。
async 函數(shù)就是隧道盡頭的亮光,很多人認(rèn)為它是異步操作的終極解決方案。
二、async 函數(shù)是什么?
一句話,async 函數(shù)就是 Generator 函數(shù)的語法糖。
前文有一個(gè) Generator 函數(shù),依次讀取兩個(gè)文件。
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 函數(shù),就是下面這樣。
var asyncReadFile = async function (){var f1 = await readFile('/etc/fstab');var f2 = await readFile('/etc/shells');console.log(f1.toString());console.log(f2.toString()); };一比較就會(huì)發(fā)現(xiàn),async 函數(shù)就是將 Generator 函數(shù)的星號(hào)(*)替換成 async,將 yield 替換成 await,僅此而已。
三、async 函數(shù)的優(yōu)點(diǎn)
async 函數(shù)對 Generator 函數(shù)的改進(jìn),體現(xiàn)在以下三點(diǎn)。
(1)內(nèi)置執(zhí)行器。 Generator 函數(shù)的執(zhí)行必須靠執(zhí)行器,所以才有了 co 函數(shù)庫,而 async 函數(shù)自帶執(zhí)行器。也就是說,async 函數(shù)的執(zhí)行,與普通函數(shù)一模一樣,只要一行。
var result = asyncReadFile();
(2)更好的語義。 async 和 await,比起星號(hào)和 yield,語義更清楚了。async 表示函數(shù)里有異步操作,await 表示緊跟在后面的表達(dá)式需要等待結(jié)果。
(3)更廣的適用性。 co 函數(shù)庫約定,yield 命令后面只能是 Thunk 函數(shù)或 Promise 對象,而 async 函數(shù)的 await 命令后面,可以跟 Promise 對象和原始類型的值(數(shù)值、字符串和布爾值,但這時(shí)等同于同步操作)。
四、async 函數(shù)的實(shí)現(xiàn)
async 函數(shù)的實(shí)現(xiàn),就是將 Generator 函數(shù)和自動(dòng)執(zhí)行器,包裝在一個(gè)函數(shù)里。
async function fn(args){// ... } // 等同于 function fn(args){ return spawn(function*() {// ...}); }所有的 async 函數(shù)都可以寫成上面的第二種形式,其中的 spawn 函數(shù)就是自動(dòng)執(zhí)行器。
下面給出 spawn 函數(shù)的實(shí)現(xiàn),基本就是前文自動(dòng)執(zhí)行器的翻版。
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 函數(shù)是非常新的語法功能,新到都不屬于 ES6,而是屬于 ES7。目前,它仍處于提案階段,但是轉(zhuǎn)碼器 Babel 和 regenerator 都已經(jīng)支持,轉(zhuǎn)碼后就能使用。
五、async 函數(shù)的用法
同 Generator 函數(shù)一樣,async 函數(shù)返回一個(gè) Promise 對象,可以使用 then 方法添加回調(diào)函數(shù)。當(dāng)函數(shù)執(zhí)行的時(shí)候,一旦遇到 await 就會(huì)先返回,等到觸發(fā)的異步操作完成,再接著執(zhí)行函數(shù)體內(nèi)后面的語句。
下面是一個(gè)例子。
上面代碼是一個(gè)獲取股票報(bào)價(jià)的函數(shù),函數(shù)前面的async關(guān)鍵字,表明該函數(shù)內(nèi)部有異步操作。調(diào)用該函數(shù)時(shí),會(huì)立即返回一個(gè)Promise對象。
下面的例子,指定多少毫秒后輸出一個(gè)值。
上面代碼指定50毫秒以后,輸出"hello world"。
六、注意點(diǎn)
await 命令后面的 Promise 對象,運(yùn)行結(jié)果可能是 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 函數(shù)之中,如果用在普通函數(shù),就會(huì)報(bào)錯(cuò)。
async function dbFuc(db) {let docs = [{}, {}, {}];// 報(bào)錯(cuò)docs.forEach(function (doc) {await db.post(doc);}); }上面代碼會(huì)報(bào)錯(cuò),因?yàn)?await 用在普通函數(shù)之中了。但是,如果將 forEach 方法的參數(shù)改成 async 函數(shù),也有問題。
async function dbFuc(db) {let docs = [{}, {}, {}];// 可能得到錯(cuò)誤結(jié)果docs.forEach(async function (doc) {await db.post(doc);}); }上面代碼可能不會(huì)正常工作,原因是這時(shí)三個(gè) db.post 操作將是并發(fā)執(zhí)行,也就是同時(shí)執(zhí)行,而不是繼發(fā)執(zhí)行。正確的寫法是采用 for 循環(huán)。
async function dbFuc(db) {let docs = [{}, {}, {}];for (let doc of docs) {await db.post(doc);} }如果確實(shí)希望多個(gè)請求并發(fā)執(zhí)行,可以使用 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); }總結(jié)
- 上一篇: SQL盲注详解
- 下一篇: 丁丁打折网卷能用吗_微信群控还能用吗?现