理解async/await

首先明確一個問題,爲何 Node.js 須要異步編程?ajax

JavaScript 是單線程的,在發出一個調用時,在沒有獲得結果以前,該調用就不返回,意思就是調用者主動等待調用結果,換句話說,就是必須等待上一個任務執行完才能執行下一個任務,這種執行模式叫:同步
Node.js 的主要應用場景是處理高併發(單位時間內極大的訪問量)和 I/O 密集場景(ps: I/O 操做每每很是耗時,因此異步的關鍵在於解決 I/O 耗時問題),若是採用同步編程,問題就來了,服務器處理一個 I/O 請求須要大量的時間,後面的請求都將排隊,形成瀏覽器端的卡頓。異步編程能解決這個問題。
所謂異步,就是調用在發出後,這個調用就直接返回了,調用者不會當即獲得結果,可是不會阻塞,能夠繼續執行後續操做,而被調用者執行獲得結果後經過狀態、事件來通知調用者使用回調函數( callback )來處理這個結果。Node在處理耗時的 I/O 操做時,將其交給其餘線程處理,本身繼續處理其餘訪問請求,當 I/O 操做處理好後就會經過事件通知 Node 用回調作後續處理。
有個例子很是好:編程

你打電話問書店老闆有沒有《分佈式系統》這本書,若是是同步通訊機制,書店老闆會說,你稍等,」我查一下",而後開始查啊查,等查好了(多是5秒,也多是一天)告訴你結果(返回結果)。而異步通訊機制,書店老闆直接告訴你我查一下啊,查好了打電話給你,而後直接掛電話了(不返回結果)。而後查好了,他會主動打電話給你。在這裏老闆經過「回電」這種方式來回調。

下面幾種方式是異步解決方案的進化過程:segmentfault

CallBacks

回調函數就是函數A做爲參數傳遞給函數B,而且在將來某一個時間被調用。callback的異步模式最大的問題就是,理解困難加回調地獄(callback hell),看下面的代碼的執行順序:promise

A();
ajax('url1', function(){
    B();
    ajax('url2', function(){
        C();
    }
    D();
});
E();

其執行順序爲:A => E => B => D => C,這種執行順序的確會讓人頭腦發昏,另外因爲因爲多個異步操做之間每每會耦合,只要中間一個操做須要修改,那麼它的上層回調函數和下層回調函數均可能要修改,這就陷入了回調地獄。而 Promise 對象就很好的解決了異步操做之間的耦合問題,讓咱們能夠用同步編程的方式去寫異步操做。瀏覽器

Promise

Promise 對象是一個構造函數,用來生成promise實例。Promise 表明一個異步操做,有三種狀態:pending,resolved(異步操做成功由 pending 變爲 resolved ),rejected(異步操做失敗由 pending 變爲 rejected ),一旦變爲後兩種狀態將不會再改變。Promise 對象做爲構造函數接受一個函數做爲參數,而這個函數又接受 resolve reject 兩個函數作爲參數,這兩個函數是JS內置的,無需配置。resolve 函數在異步操做成功後調用,將pending狀態變爲resolved,並將它的參數傳遞給回調函數;reject 函數在異步操做失敗時調用,將pending狀態變爲rejected,並將參數傳遞給回調函數。服務器

  • Promise.prototype.then()

Promise構造函數的原型上有一個then方法,它接受兩個函數做爲參數,分別是 resolved 狀態和 rejected 狀態的回調函數,而這兩個回調函數接受的參數分別是Promise實例中resolve函數和reject函數中的參數。 另外rejected狀態的回調函數是可省略的。併發

下面是一個使用示例:異步

const instance = new Promise((resolve, reject) => {
    // 一些異步操做
    if(/*異步操做成功*/) {
      resolve(value);
    } else {
      reject(error);
    }
  }
})
instance.then(value => {
  // do something...
}, error => {
  // do something...
})

注意Promise實例在生成後會當即執行,而 then 方法只有在全部同步任務執行完後纔會執行,看看下面的例子:async

const promise = new Promise((resolve, reject) => {
  console.log('async task begins!');
  setTimeout(() => {
    resolve('done, pending -> resolved!');
  }, 1000);
})

promise.then(value => {
  console.log(value);
}) 

console.log('1.please wait');
console.log('2.please wait');
console.log('3.please wait');
// async task begins!
// 1.please wait
// 2.please wait
// 3.please wait
// done, pending -> resolved!

上面的實例能夠看出,Promise實例生成後當即執行,因此首先輸出 'async task begins!',隨後定義了一個異步操做 setTimeout,1秒後執行,因此無需等待,向下執行,而then方法指定的回調函數要在全部同步任務執行完後才執行,因此先輸出了3個'please wait',最後輸出'done, pending -> resolved!'。(此處省略了then方法中的reject回調,通常不在then中作rejected狀態的處理,而使用catch方法專門處理錯誤,至關於.then(null, reject))分佈式

  • 鏈式調用 then 方法

    then 方法會返回一個新的 Promise 實例,能夠分兩種狀況來看:

  1. 指定返回值是新的 Promise 對象,如return new Promise(...),這種狀況沒啥好說的,因爲返回的是 Promise,後面顯然能夠繼續調用then方法。
  2. 返回值不是Promise, 如:return 1 這種狀況仍是會返回一個 Promise,而且這個Promise 當即執行回調 resolve(1)。因此仍然能夠鏈式調用then方法。(注:若是沒有指定return語句,至關於返回了undefined

使用 then 的鏈式寫法,按順序實現一系列的異步操做,這樣就能夠用同步編程的形式去實現異步操做,來看下面的例子,實現隔兩秒打一次招呼:

function sayHi(name) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(name);
    }, 2000)
  })
}

sayHi('張三')
  .then(name => {
    console.log(`你好, ${name}`);
    return sayHi('李四');    // 最終 resolved 函數中的參數將做爲值傳遞給下一個then
  })
  // name 是上一個then傳遞出來的參數
  .then(name => {                
    console.log(`你好, ${name}`);
    return sayHi('王二麻子');
  })
  .then(name => {
    console.log(`你好, ${name}`);
  })
// 你好, 張三
// 你好, 李四
// 你好, 王二麻子

能夠看到使用鏈式then的寫法,將異步操做變成了同步的形式,可是也帶來了新的問題,就是異步操做變成了很長的then鏈,新的解決方法就是Generator,這裏跨過它直接說它的語法糖:async/await

async/await

  • async

async/await其實是Generator的語法糖。顧名思義,async關鍵字表明後面的函數中有異步操做,await表示等待一個異步方法執行完成。聲明異步函數只需在普通函數前面加一個關鍵字async便可,如:

async function funcA() {}

async 函數返回一個Promise對象(若是指定的返回值不是Promise對象,也返回一個Promise,只不過當即 resolve ,處理方式同 then 方法),所以 async 函數經過 return 返回的值,會成爲 then 方法中回調函數的參數:

async function funcA() {
  return 'hello!';
}

funcA().then(value => {
  console.log(value);
})
// hello!

單獨一個 async 函數,其實與Promise執行的功能是同樣的,來看看 await 都幹了些啥。

  • await

顧名思義, await 就是異步等待,它等待的是一個Promise,所以 await 後面應該寫一個Promise對象,若是不是Promise對象,那麼會被轉成一個當即 resolve 的Promise。 async 函數被調用後就當即執行,可是一旦遇到 await 就會先返回,等到異步操做執行完成,再接着執行函數體內後面的語句。總結一下就是:async函數調用不會形成代碼的阻塞,可是await會引發async函數內部代碼的阻塞。看看下面這個例子:

async function func() {
  console.log('async function is running!');
  const num1 = await 200;
  console.log(`num1 is ${num1}`);
  const num2 = await num1+ 100;
  console.log(`num2 is ${num2}`);
  const num3 = await num2 + 100;
  console.log(`num3 is ${num3}`);
}

func();
console.log('run me before await!');
// async function is running!
// run me before await!
// num1 is 200
// num2 is 300
// num3 is 400

能夠看出調用 async func 函數後,它會當即執行,首先輸出了'async function is running!',接着遇到了 await 異步等待,函數返回,先執行func()後面的同步任務,同步任務執行完後,接着await等待的位置繼續往下執行。能夠說,async函數徹底能夠看做多個異步操做,包裝成的一個Promise 對象,而await命令就是內部then命令的語法糖。

值得注意的是, await 後面的 Promise 對象不老是返回 resolved 狀態,只要一個 await 後面的Promise狀態變爲 rejected ,整個 async 函數都會中斷執行,爲了保存錯誤的位置和錯誤信息,咱們須要用 try...catch 語句來封裝多個 await 過程,以下:

async function func() {
  try {
    const num1 = await 200;
    console.log(`num1 is ${num1}`);
    const num2 = await Promise.reject('num2 is wrong!');
    console.log(`num2 is ${num2}`);
    const num3 = await num2 + 100;
    console.log(`num3 is ${num3}`);
  } catch (error) {
    console.log(error);
  }
}

func();
// num1 is 200
// 出錯了
// num2 is wrong!

如上所示,在 num2 await 獲得了一個狀態爲 rejected 的Promise對象,該錯誤會被傳遞到 catch 語句中,這樣咱們就能夠定位錯誤發生的位置。

  • async/await比Promise強在哪兒?

接下來咱們用async/await改寫一下Promise章節中關於sayHi的一個例子,代碼以下:

function sayHi(name) {
  return new Promise((resolved, rejected) => {
    setTimeout(() => {
      resolved(name);
    }, 2000)
  })
}

async function sayHi_async(name) {
  const sayHi_1 = await sayHi(name)
  console.log(`你好, ${sayHi_1}`)
  const sayHi_2 = await sayHi('李四')
  console.log(`你好, ${sayHi_2}`)
  const sayHi_3 = await sayHi('王二麻子')
  console.log(`你好, ${sayHi_3}`)
}

sayHi_async('張三')
// 你好, 張三
// 你好, 李四
// 你好, 王二麻子

與以前長長的then鏈和then方法裏的回調函數相比,這樣的寫法看起來像是同步寫法而且更加清爽,更加符合編程習慣。

參考文章

https://segmentfault.com/a/11...
https://segmentfault.com/a/11...
https://www.zhihu.com/questio...

相關文章
相關標籤/搜索