一、含義
在早期纳令,ES6 最初的異步處理方案引入 Promise 對(duì)象挽荠,后經(jīng)過(guò)升級(jí)成更好的異步編程解決方案:Generator 函數(shù)。
但是平绩,Generator 函數(shù)是不能自動(dòng)執(zhí)行的圈匆,返回的是一個(gè)遍歷器對(duì)象。需要借助 Thunk 函數(shù)或者 co 模塊實(shí)現(xiàn)自動(dòng)流程管理捏雌。
為了使得異步操作變得更加合理臭脓,ES2017標(biāo)準(zhǔn)引入了 async 函數(shù)。相較于 Generator 函數(shù)腹忽,async 函數(shù)有一下幾點(diǎn)優(yōu)勢(shì):
1. 內(nèi)置執(zhí)行器
async 函數(shù)自帶執(zhí)行器来累,不需要像 Generator 函數(shù)需要 co 模塊實(shí)現(xiàn)自動(dòng)流程管理。像調(diào)用普通函數(shù)一樣:asyncReadFile() 窘奏,只要一行嘹锁,Generator 函數(shù)需要調(diào)用 next 方法或者借助 co 模塊才能得到最終結(jié)果。
2. 更好的語(yǔ)義性
async 和 await 比起星號(hào)和 yield着裹,語(yǔ)義更清楚领猾。async 表示函數(shù)里有異步操作,await 表示緊跟在后面的表達(dá)式需要等待結(jié)果骇扇。
3. 更廣的適用性
co 模塊約定摔竿,yield 命令后面只能是 Thunk 函數(shù) 或 Promise對(duì)象,而 async 函數(shù)的 await 命令后面少孝,可以是 Promise 對(duì)象和原始類(lèi)型的值(數(shù)值继低、字符串、布爾值稍走,但這時(shí)等同于同步操作)袁翁。
4. 返回值是 Promise
async 函數(shù)的 返回值是 Promise對(duì)象柴底,這比 Generator 函數(shù)的返回值是 Iterator 對(duì)象方便許多,可以用 then 方法指定下一步操作粱胜。
通俗講柄驻,async 函數(shù)是 Generator 函數(shù)的語(yǔ)法糖,是由多個(gè)異步操作包裝成的一個(gè) Promise 對(duì)象焙压,而 await 命令就是內(nèi)部 then 命令的語(yǔ)法糖鸿脓。
二、用法
1涯曲、多種使用形式
// 函數(shù)聲明
async function foo() {};
// 函數(shù)表達(dá)式
const = foo = async function() {};
// 對(duì)象的方法
let obj = { async foo() {} };
obj.foo().then(...);
// calss 的方法
class Storage {
constructor() {
this.cachePromise = caches.open('yuan');
}
async getYuan(name) {
const cache = await this.cachePromise;
return cache.match(`/yuan/${ name } .png`);
}
}
const storage = new Storage();
storage.getYuan('monkey').then(...);
// 箭頭函數(shù)
const foo = async () => {};
2答憔、返回 Promise 對(duì)象
async 函數(shù)內(nèi)部 return 語(yǔ)句返回的值,會(huì)成為 then 方法回調(diào)函數(shù)的參數(shù)
async function f() {
return "hello world";
}
f().then(v => console.log(v)); // hello world
3掀抹、Promise 對(duì)象的狀態(tài)變化
只有 async 函數(shù)內(nèi)部的 await 命令執(zhí)行完,才會(huì)執(zhí)行 then 方法指定的回調(diào)函數(shù):
async function getTitle(url) {
let response = await fetch(url);
let html = response.text(0;
return html..match(/<title>([\s\S]+<\title>/i)[1];
}
getTitle("http://www.reibang.com/writer#/notebooks/15137721/notes/24616981/preview").then(...);
上述代碼中 getTitle 函數(shù)內(nèi)部的3個(gè)操作:抓取網(wǎng)頁(yè)心俗、取出文本傲武。匹配頁(yè)面標(biāo)題。只有執(zhí)行完這三個(gè)操作城榛,才能執(zhí)行 then 方法里的操作揪利。
4、await 命令
await 命令后面是一個(gè) Promise 對(duì)象狠持,如果不是疟位,會(huì)自動(dòng)被轉(zhuǎn)為一個(gè) resolve 的 Promise 對(duì)象:
async function f() {
return await 'yuan';
}
f().then(v => console.log(v)); // yuan
await 命令后沒(méi)的 Promise 對(duì)象如果變?yōu)?reject 狀態(tài),則 reject 的參數(shù)會(huì)被 catch 方法的回調(diào)函數(shù)接收到:
async function foo() {
await Promise.reject('報(bào)錯(cuò)了');
}
foo()
.then(v => console.log(v))
.catch( e => console.log(e))
// 瀏覽器拋出錯(cuò)誤
只要有一個(gè) await 命令后面的 Promise 對(duì)象變?yōu)?reject喘垂,那么整個(gè) async 函數(shù)都會(huì)中斷甜刻。
三、使用注意點(diǎn)
1正勒、await 命令后面的 Promise 對(duì)象運(yùn)行的結(jié)果可能是 reject 的狀態(tài)得院,所以最好把 await 命令放在 try...catch 代碼塊里,或者在
await 命令后加一個(gè) catch 方法:
// await 命令寫(xiě)在 try...catch
async function f() {
try {
await somePromise();
}
catch(err) {
console.log(err);
}
}
// 在 await 后加 catch
async function f() {
await somePromise()
.catch (function (err) {
console.log(err)
});
}
2章贞、多個(gè) await 名利后面的異步操作如果不存在繼發(fā)關(guān)系祥绞,最好讓他們同時(shí)出發(fā)
let foo = await getFoo();
let bar = await getBar();
3、await 命令只能用在 async 函數(shù)之中鸭限,如果用在普通函數(shù)中會(huì)報(bào)錯(cuò)
4蜕径、多個(gè)請(qǐng)求并發(fā)執(zhí)行,可以使用 Promise.all 方法:
async function dbFunc(db) {
let docs = [{}, {}, {}];
let promises = doc.map((doc) = > db.post(doc));
let results = await Promise.all(promises);
console.log(results);
}
四败京、異步應(yīng)用對(duì)比:Promise兜喻、Generator、async
以 setTimeout 來(lái)模擬異步操作:
function foo (obj) {
return new Promise((resolve, reject) => {
window.setTimeout(() => {
let data = {
height: 180
}
data = Object.assign({}, obj, data)
resolve(data)
}, 1000)
})
}
function bar (obj) {
return new Promise((resolve, reject) => {
window.setTimeout(() => {
let data = {
talk () {
console.log(this.name, this.height);
}
}
data = Object.assign({}, obj, data)
resolve(data)
}, 1500)
})
}
兩個(gè)函數(shù)都返回了 Promise 對(duì)象赡麦,使用 Object.assign() 方法合并傳遞過(guò)來(lái)的參數(shù)虹统。
Promise 實(shí)現(xiàn)異步:
function main () {
return new Promise((resolve, reject) => {
const data = {
name: 'keith'
}
resolve(data)
})
}
main().then(data => {
foo(data).then(res => {
bar(res).then(data => {
return data.talk() // keith 180
})
})
})
此方法的缺點(diǎn):語(yǔ)義不好弓坞,不夠直觀(guān),使用多個(gè) then 方法车荔,有可能出現(xiàn)回調(diào)地獄渡冻。
Generator 函數(shù)實(shí)現(xiàn)異步:
function *gen () {
const data = {
name: 'keith'
}
const fooData = yield foo(data)
const barData = yield bar(fooData)
return barData.talk()
}
function run (gen) {
const g = gen()
const next = data => {
let result = g.next(data)
if (result.done) return result.value
result.value.then(data => {
next(data)
})
}
next()
}
run(gen)
Generator 函數(shù)相較于 Promise 實(shí)現(xiàn)異步操作,優(yōu)點(diǎn)在于:異步過(guò)程同步化忧便,避免回調(diào)地獄的出現(xiàn)族吻。缺點(diǎn)在于:需要借助run 函數(shù)或者 co 模塊實(shí)現(xiàn)流程的自動(dòng)管理。
async 函數(shù)實(shí)現(xiàn)異步:
async function main () {
const data = {
name: 'keith'
}
const fooData = await foo(data)
const barData = await bar(fooData)
return barData
}
main().then(data => {
data.talk()
})
async 函數(shù)優(yōu)點(diǎn)在于:實(shí)現(xiàn)了執(zhí)行器的內(nèi)置珠增,代碼量減少超歌,且異步過(guò)程同步化,語(yǔ)義化更強(qiáng)蒂教。
五巍举、應(yīng)用實(shí)例
1、從豆瓣API上獲取數(shù)據(jù):
var fetchDoubanApi = function() {
return new Promise((resolve, reject) = >{
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
var response;
try {
response = JSON.parse(xhr.responseText);
} catch(e) {
reject(e);
}
if (response) {
resolve(response, xhr.status, xhr);
}
} else {
reject(xhr);
}
}
};
xhr.open('GET', 'https://api.douban.com/v2/user/aisk', true);
xhr.setRequestHeader("Content-Type", "text/plain");
xhr.send(data);
});
};
(async function() {
try {
let result = await fetchDoubanApi();
console.log(result);
} catch(e) {
console.log(e);
}
})();
2凝垛、根據(jù)電影名稱(chēng)懊悯,下載對(duì)應(yīng)的海報(bào)
import fs from 'fs';
import path from 'path';
import request from 'request';
var movieDir = __dirname + '/movies',
exts = ['.mkv', '.avi', '.mp4', '.rm', '.rmvb', '.wmv'];
// 讀取文件列表
var readFiles = function() {
return new Promise(function(resolve, reject) {
fs.readdir(movieDir,
function(err, files) {
resolve(files.filter((v) = >exts.includes(path.parse(v).ext)));
});
});
};
// 獲取海報(bào)
var getPoster = function(movieName) {
let url = `https: //api.douban.com/v2/movie/search?q=${encodeURI(movieName)}`;
return new Promise(function(resolve, reject) {
request({
url: url,
json: true
},
function(error, response, body) {
if (error) return reject(error);
resolve(body.subjects[0].images.large);
})
});
};
// 保存海報(bào)
var savePoster = function(movieName, url) {
request.get(url).pipe(fs.createWriteStream(path.join(movieDir, movieName + '.jpg')));
};
(async() = >{
let files = await readFiles();
// await只能使用在原生語(yǔ)法
for (var file of files) {
let name = path.parse(file).name;
console.log(`正在獲取【$ {
name
}】的海報(bào)`);
savePoster(name, await getPoster(name));
}
console.log('=== 獲取海報(bào)完成 ===');
})();
六、結(jié)語(yǔ)
本章梦皮,我們需要對(duì)比 Promise 和 Generator 異步操作的優(yōu)勢(shì)了解 async 函數(shù)的由來(lái)炭分,掌握 async 函數(shù)的用法及其注意事項(xiàng),再結(jié)合相應(yīng)的實(shí)例深入了解 async 函數(shù)的實(shí)現(xiàn)原理剑肯。下一章捧毛,我們來(lái)了解 ES6 另一個(gè)語(yǔ)法糖: class,盡情期待吧让网!
章節(jié)目錄
1呀忧、ES6中啥是塊級(jí)作用域?運(yùn)用在哪些地方溃睹?
2荐虐、ES6中使用解構(gòu)賦值能帶給我們什么?
3丸凭、ES6字符串?dāng)U展增加了哪些福扬?
4、ES6對(duì)正則做了哪些擴(kuò)展惜犀?
5铛碑、ES6數(shù)值多了哪些擴(kuò)展?
6虽界、ES6函數(shù)擴(kuò)展(箭頭函數(shù))
7汽烦、ES6 數(shù)組給我們帶來(lái)哪些操作便利?
8莉御、ES6 對(duì)象擴(kuò)展
9撇吞、Symbol 數(shù)據(jù)類(lèi)型在 ES6 中起什么作用俗冻?
10、Map 和 Set 兩數(shù)據(jù)結(jié)構(gòu)在ES6的作用
11牍颈、ES6 中的Proxy 和 Reflect 到底是什么鬼迄薄?
12、從 Promise 開(kāi)始踏入異步操作之旅
13煮岁、ES6 迭代器(Iterator)和 for...of循環(huán)使用方法
14讥蔽、ES6 異步進(jìn)階第二步:Generator 函數(shù)
15、JavaScript 異步操作進(jìn)階第三步:async 函數(shù)
16画机、ES6 構(gòu)造函數(shù)語(yǔ)法糖:class 類(lèi)