非同步程式設計的 async/await
async/await 和 Generators + co 的寫法非常的相似,只是把用於宣告 Generator 函式的 * 關鍵字替換成了 async 並寫在了 function 關鍵字的前面,把 yield 關鍵字替換成了 await;另外,async 函式是基於 Promise 的,await 關鍵字後面等待的非同步操作必須是一個 Promise 例項,當然也可以是原始型別的值,只不過這時的執行效果等同於同步,與 Generator 不同的是,await 關鍵字前可以使用變數去接收這個正在等待的 Promise 例項執行後的結果。
async 函式的基本用法
async 函式返回一個 Promise 例項,可以使用 then 方法添加回調函式。當函式執行的時候,只要遇到 await 就會等待,直到 await 後面的同步或非同步操作完成,再接著執行函式體內後面的語句。
ES6的宣告
// 1- 函式宣告
function * fn() {}
// 2- 函式表示式
const * fn = function() {};
ES7的宣告
// 1- 函式宣告
async function fn() {}
// 2- 函式表示式
const fn = async function() {};
// 3- 箭頭函式
const fn = async () => {};
// 4- 作為物件的方法
let obj = {
async fn() {}
};
// 5- 作為 class 的方法
class Person(name) {
constructor () {
this .name = name;
}
async getName() {
const name = await this.name;
return name;
}
}
使用 NodeJS 的 fs 模組連續非同步讀檔案,第一個檔名為 a.txt,讀到的內容為 b.txt,作為要讀的第二個檔案的檔名,繼續讀 b.txt 後將讀到的內容 “Hello world” 打印出來。
我們來使用 async/await 的方式來實現一下:
// 引入依賴
let fs = require("fs");
let util = require("util" );
// 將 fs.readFile 轉換成 Promise
let readFile = util.promisify(fs.readFile);
// 宣告 async 函式
async function read(file) {
let aData = await readFile(file, "utf8");
let bData = await readFile(aData, "utf8");
return bData;
}
// 呼叫 async 函式
read("a.txt").then(data => {
console.log(data); // Hello world
});
與 Generator 函式一樣,寫法像同步,執行是非同步,不同的是我們即沒有手動呼叫 next 方法,也沒有藉助 co 庫,其實是 async 函式內部集成了類似於 co 的執行器,幫我們在非同步完成後自動向下執行程式碼,所以說 async/await 是 Generators + co 的語法糖。
await 非同步併發
在 async 函式中,如果有多個 await 互不依賴,這種情況下如果執行一個,等待一個完成,再執行一個,再等待完成,這樣是很浪費效能的,所以我們要把這些非同步操作同時觸發。
假設我們非同步讀取兩個檔案,且這兩個檔案不相關,我可以使用下面的方式來實現:
// 前置
let fs = require("fs");
let util = require("util");
let readFile = util.promisify(fs.readFile);
// 方法1- 需要改進的 async 函式
async function fn() {
let aData = await readFile("a.txt", "utf8");
let bData = await readFile("b.txt", "utf8");
return [aData, bData];
}
fn();
// 方法2- 在 async 函式外部觸發非同步
let aDataPromise = readFile("a.txt", "utf8");
let bDataPromise = readFile("b.txt", "utf8");
async function fn() {
let aData = await aDataPromise;
let bData = await bDataPromise;
return [aData, bData];
}
fn();
// 方法3- 使用 Promise.all
async function fn() {
let dataArr = await Promise.all(
readFile("a.txt", "utf8"),
readFile("a.txt", "utf8")
);
return dataArr;
}
fn();
非同步併發例項
// 建立 Promise 例項
let p1 = Promise.resolve("p1 success");
let p2 = Promise.resolve("p2 success");
let p3 = Promise.resolve("p3 success");
// 1-錯誤的處理方式 async 函式
async function fn(promises) {
promise.forEach(function (promise) {
await promise;
});
}
fn([p1, p2, p3]); // 執行時報錯
// 2-正確的開啟方式 修改方式
async function fn(promises) {
for(let i = 0; i < promises.length; i++) {
await promises[i];
}
}
fn([p1, p2, p3]); // 正常執行