async函數,瞭解一下

今天,帶你們來談談ES6中的async函數,咱們在理解一個概念的時候,無外乎這是三個方面html

  • 是什麼
  • 爲何
  • 怎麼用

若是感受文章太長,能夠直接拉到下面,看小結🙄前端

sync是什麼

ES7提供了async函數,使得異步操做變得更加方便。async函數是什麼?一句話,async函數就是Generator函數的語法糖。node

咱們來個案例,取讀文件git

Generator函數github

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函數,就是下面這樣。shell

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,僅此而已。bash

咱們會想,爲何明明有Generator函數,還須要async函數微信

爲何須要async函數

async函數對 Generator 函數的改進,體如今如下四點。異步

(1)內置執行器。Generator函數的執行必須靠執行器,因此纔有了co模塊,而async函數自帶執行器。也就是說,async函數的執行,與普通函數如出一轍,只要一行。async

var result = asyncReadFile();
複製代碼

上面的代碼調用了asyncReadFile函數,而後它就會自動執行,輸出最後結果。這徹底不像Generator函數,須要調用next方法,或者用co模塊,才能獲得真正執行,獲得最後結果。

(2)更好的語義。async和await,比起星號和yield,語義更清楚了。async表示函數裏有異步操做,await表示緊跟在後面的表達式須要等待結果。

(3)更廣的適用性。 co模塊約定,yield命令後面只能是Thunk函數或Promise對象,而async函數的await命令後面,能夠是Promise對象和原始類型的值(數值、字符串和布爾值,但這時等同於同步操做)。

(4)返回值是Promise。async函數的返回值是Promise對象,這比Generator函數的返回值是Iterator對象方便多了。你能夠用then方法指定下一步的操做。

進一步說,async函數徹底能夠看做多個異步操做,包裝成的一個Promise對象,而await命令就是內部then命令的語法糖。

語法

async怎麼使用呢?

一個函數前面加上async,就可讓這個函數數成爲異步函數,跳出本來的執行順序

console.log(1)
async function asyfun () {
   console.log(2)
}
asyfun();
console.log(3)

// 打印結果:1,3,2
複製代碼

(1)async函數返回一個Promise對象。

async函數內部return語句返回的值,會成爲then方法回調函數的參數。

async function f() {
  return 'hello world';
}

f().then(v => console.log(v))
// "hello world"
複製代碼

上面代碼中,函數f內部return命令返回的值,會被then方法回調函數接收到。

async函數內部拋出錯誤,會致使返回的Promise對象變爲reject狀態。拋出的錯誤對象會被catch方法回調函數接收到。

async function f() {
  throw new Error('出錯了');
}

f().then(
  v => console.log(v),
  e => console.log(e)
)
// Error: 出錯了
複製代碼

(2)async函數返回的Promise對象,必須等到內部全部await命令的Promise對象執行完,纔會發生狀態改變。也就是說,只有async函數內部的異步操做執行完,纔會執行then方法指定的回調函數。

下面是一個例子。

async function getTitle(url) {
  let response = await fetch(url);
  let html = await response.text();
  return html.match(/<title>([\s\S]+)<\/title>/i)[1];
}
getTitle('https://tc39.github.io/ecma262/').then(console.log)
// "ECMAScript 2017 Language Specification"

複製代碼

(3)正常狀況下,await命令後面是一個Promise對象。若是不是,會被轉成一個當即resolve的Promise對象。

async function f() {
  return await 123;
}

f().then(v => console.log(v))
// 123
複製代碼

上面代碼中,await命令的參數是數值123,它被轉成Promise對象,並當即resolve。

await命令後面的Promise對象若是變爲reject狀態,則reject的參數會被catch方法的回調函數接收到。

async function f() {
  await Promise.reject('出錯了');
}

f()
.then(v => console.log(v))
.catch(e => console.log(e))
// 出錯了
複製代碼

注意,上面代碼中,await語句前面沒有return,可是reject方法的參數依然傳入了catch方法的回調函數。這裏若是在await前面加上return,效果是同樣的。

只要一個await語句後面的Promise變爲reject,那麼整個async函數都會中斷執行。

async function f() {
  await Promise.reject('出錯了');
  await Promise.resolve('hello world'); // 不會執行
}
複製代碼

上面代碼中,第二個await語句是不會執行的,由於第一個await語句狀態變成了reject。

爲了不這個問題,能夠將第一個await放在try...catch結構裏面,這樣第二個await就會執行。

async function f() {
  try {
    await Promise.reject('出錯了');
  } catch(e) {
  }
  return await Promise.resolve('hello world');
}

f()
.then(v => console.log(v))
// hello world

複製代碼

另外一種方法是await後面的Promise對象再跟一個catch方面,處理前面可能出現的錯誤。

async function f() {
  await Promise.reject('出錯了')
    .catch(e => console.log(e));
  return await Promise.resolve('hello world');
}

f()
.then(v => console.log(v))
// 出錯了
// hello world
複製代碼

若是有多個await命令,能夠統一放在try...catch結構中。

async function main() {
  try {
    var val1 = await firstStep();
    var val2 = await secondStep(val1);
    var val3 = await thirdStep(val1, val2);

    console.log('Final: ', val3);
  }
  catch (err) {
    console.error(err);
  }
}
複製代碼

(4)若是await後面的異步操做出錯,那麼等同於async函數返回的Promise對象被reject。

async function f() {
  await new Promise(function (resolve, reject) {
    throw new Error('出錯了');
  });
}

f()
.then(v => console.log(v))
.catch(e => console.log(e))
// Error:出錯了
複製代碼

上面代碼中,async函數f執行後,await後面的Promise對象會拋出一個錯誤對象,致使catch方法的回調函數被調用,它的參數就是拋出的錯誤對象。具體的執行機制,能夠參考後文的「async函數的實現」。

防止出錯的方法,也是將其放在try...catch代碼塊之中。

async function f() {
  try {
    await new Promise(function (resolve, reject) {
      throw new Error('出錯了');
    });
  } catch(e) {
  }
  return await('hello world');
}
複製代碼

小結

  1. async比generstor更好,語法上更語義化,內置執行器
  2. async返回值是Promise,函數內部的return值,會成爲then方法回調函數的參數。
  3. await只能在async函數內部使用
  4. 若是await後面的異步操做出錯,那麼等同於async函數返回的Promise對象被reject

本文首發於微信公衆號:node前端

相關文章
相關標籤/搜索