JavaScript 異步操作進(jìn)階第三步:async 函數(shù)

一、含義

在早期纳令,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)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末冶伞,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子步氏,更是在濱河造成了極大的恐慌响禽,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,430評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件荚醒,死亡現(xiàn)場(chǎng)離奇詭異芋类,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)腌且,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,406評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)榛瓮,“玉大人铺董,你說(shuō)我怎么就攤上這事≠飨” “怎么了精续?”我有些...
    開(kāi)封第一講書(shū)人閱讀 167,834評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)粹懒。 經(jīng)常有香客問(wèn)我重付,道長(zhǎng),這世上最難降的妖魔是什么凫乖? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,543評(píng)論 1 296
  • 正文 為了忘掉前任确垫,我火速辦了婚禮,結(jié)果婚禮上帽芽,老公的妹妹穿的比我還像新娘删掀。我一直安慰自己,他們只是感情好导街,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,547評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布披泪。 她就那樣靜靜地躺著,像睡著了一般搬瑰。 火紅的嫁衣襯著肌膚如雪款票。 梳的紋絲不亂的頭發(fā)上控硼,一...
    開(kāi)封第一講書(shū)人閱讀 52,196評(píng)論 1 308
  • 那天,我揣著相機(jī)與錄音艾少,去河邊找鬼卡乾。 笑死,一個(gè)胖子當(dāng)著我的面吹牛姆钉,可吹牛的內(nèi)容都是我干的说订。 我是一名探鬼主播,決...
    沈念sama閱讀 40,776評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼潮瓶,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼陶冷!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起毯辅,我...
    開(kāi)封第一講書(shū)人閱讀 39,671評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤埂伦,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后思恐,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體沾谜,經(jīng)...
    沈念sama閱讀 46,221評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,303評(píng)論 3 340
  • 正文 我和宋清朗相戀三年胀莹,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了基跑。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,444評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡描焰,死狀恐怖媳否,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情荆秦,我是刑警寧澤篱竭,帶...
    沈念sama閱讀 36,134評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站步绸,受9級(jí)特大地震影響掺逼,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜瓤介,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,810評(píng)論 3 333
  • 文/蒙蒙 一吕喘、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧刑桑,春花似錦兽泄、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,285評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春蜓陌,著一層夾襖步出監(jiān)牢的瞬間觅彰,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,399評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工钮热, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留填抬,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,837評(píng)論 3 376
  • 正文 我出身青樓隧期,卻偏偏與公主長(zhǎng)得像飒责,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子仆潮,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,455評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容