ES6&ES7中的異步之async函數

async函數

定義

async函數其實就是以前說過的Generator的語法糖,用於實現異步操做。它是ES2017的新標準。數組

讀取兩個文件:併發

const fs = require('fs')

const readFile = function(filename){
    return new Promise(function(resolve,reject){
        fs.readFile(filename,function(error,data)){
            if(error) return reject(error)
            resolve(data)
        }
    })
}


const gen = function* (){
    const gen1 = yield readFile('../1.txt')
    const gen2 = yield readFile('../2.txt')
    console.log(gen1.toString())
    console.log(gen2.toString())
}

若是使用async函數的話,會是這麼寫異步

const _readFile = async function(){
    const r1 = await readFile('../3.txt')
    const r2 = await readFile('../4.txt')
    console.log(r1.toString())
    console.log(r2.toString())
}

通常狀況下,只是把Generator的*換成async,把yield換成await。
async函數是Generator函數的改進,具體體如今四點async

1.內置執行器:函數

相對於Generator函數須要co模塊或者next()方法來做爲執行器執行,async函數自帶執行器,因此上邊的代碼只須要一句fetch

_readFile()

能夠執行。ui

2.更好的語義:url

async和await分別表示異步和等待,比起*和yield更容易理解。spa

3.更普遍的適用性:
yield後邊只能跟Thunk函數或者Promise對象,可是在async函數中,能夠跟Promise對象和基本數據類型。code

4.返回值是Promise

相比於Generator函數返回一個Iterator還須要遍歷,async直接返回一個Promise能夠直接調用then方法和catch方法。

基本用法

async函數返回一個Promise對象,能夠使用then方法添加回調,而後使用await關鍵字後,會等到異步操做執行完在執行後邊的語句。

async function getPriceByName(name){
    const symbol = await getSymbod(name)
    const price = await getPrice(symbol)
    return price
}

getPriceByName('WUBA').then(function(result){
    console.log(result)
})

上邊的例子,是一個經過股票的名稱,得到股票的代碼,再得到價錢。前邊聲明async關鍵字,表示內部有內部操做,調用函數會返回一個Promise對象。

再看下一個例子,是一個指定多少毫秒後返回一個值。

function TimeOut(time){
    return new Promise(function(resolve)){
        setTimeout(resolve,time)
    }
}

async asyncTimeOut = function(value,time){
    const t1 = await TimeOut(time)
    console.log(t1)
}

asyncTimeOut('hello',1000)
asyncTimeOut('world',2000)

async函數的多種形式

函數表達式
const fun1 = async function(){
    ....
}

函數聲明
async function fun2(){
    ....
}

箭頭函數
const fun3 = async () => {
    ....
}

對象中的變量
let obj = {
    async fun4(){
        ....
    }
}
obj.fun4()

類的寫法
class Async {
    constructor(){}
    
    async fun5(){
        ....
    }
}
const a1 = new Async()
a1.fun5().then()

語法

返回一個Promise對象

說過不少次了,async函數返回一個Promise對象。
函數return的值將會做爲then方法的參數

async function show(){
    return '123'
}

show().then((v) => console.log(v))

show方法返回的值會被做爲then方法的參數而調用。

若是在async函數內部拋出錯誤,會被catch捕獲,這個時候Promise對象變成reject狀態。

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

show2().then(
    v => console.log(v),
    e => console.log(e)
)

Promise的狀態改變

async函數返回的Promise對象,必須等到函數內部全部的await執行完纔會發生狀態的變化,也就是說,得等到全部await執行完,纔會執行後續的then方法。

async function getText(url){
    const response = await fetch(url)
    const text = await response.text()
    return text.match('../aa/[a-z]}')[1]   //反正就是一個正則匹配
}

const url = '....'
getText(url).then(v => console.log(v))

這個例子說明,得等到兩個awiat都執行完纔會console返回的數據。

await命令

前邊說過,await命令後邊跟隨一個Promise對象。若是不是,會被轉成Promise對象。

async function show(){
    return await '123'
}

show().then((v) => console.log(v))

若是await後邊的Promise對象變成了reject狀態,會被後邊的catch()捕獲。

async function fun1() {
    return await Promise.reject('出錯了')
}

fun1().catch(e => console.log(e))

若是async函數內部有多個await,可是隻要一個await返回的Promise對象變成了reject狀態,則整個函數馬上捕獲異常。

若是想要前邊的正常拋出異常而不影響後邊的await語句執行,能夠把前邊的寫進一個try/catch中去。

async function fun2(){
    try{
        await Promise.reject('出錯了')
    }catch(e){
    }
    
    await Promise.resolev('hello')
}

fun2().then(v => console.log(v))

使用的注意點

  • 因爲await後面跟隨的是Promise對象,因此對象可能會有兩個狀態,一個resolve一個reject。因此,最好把await代碼放到try/catch語句中比較好。

async function fun3(){
    try{
        await asyncFun1()
        await asyncFun2()
    } catch(e){
        console.log(e)
    }
}

// 還有另一種寫法
async function fun4(){
    await asyncFun1().catch(e => console.log(e))
    await asyncFun2().catch(e => console.log(e))
}

仍是第一種方法更好一點。直接寫進try/catch語句中。

  • 最好讓多個await後邊的異步操做同時發生,若是不是不存在前後順序的話。

let a1 = await get1()
let a2 = await get2()

上邊的寫法,get1執行完以後纔會執行get2,若是get1和get2沒有直接的關聯,那樣會很浪費時間。

//同時觸發
let [a1,a2] = await Promise.all([get1(),get2()])
  • 若是await放到async函數以外,就會報錯,只能放到async函數內部。

async的原理

原理也很簡單,就是把Generator函數和自動執行器包裝在一個函數中。

async function fn(){
    .....
}

// 等價於
function fn(){
    return spawn(function *(){
        .....
    })
}

其中spawn函數就是自動執行器。

function spawn(genF) {
  return new Promise(function(resolve, reject) {
    const gen = genF();
    function step(nextF) {
      let next; 
      try {
        next = nextF();
      } catch(e) {
        return reject(e);
      }
      if(next.done) {
        return resolve(next.value);
      }
      Promise.resolve(next.value).then(function(v) {
        step(function() { return gen.next(v); });
      }, function(e) {
        step(function() { return gen.throw(e); });
      });
    }
    step(function() { return gen.next(undefined); });
  });
}

實例:按照順序完成異步操做

實際開發中常常會遇到各類異步操做,這裏有一個例子。一次讀取一組url,而後按照讀取的順序返回結果。

function readUrls(urls) {
    const textPromise = urls.map(url => {
        fetch(url).then(response => response.text())
    })
    
    // 按照順序讀出
    textPromise.reduce((chain,textPromise) => {
        return chain.then(() => textPromise)
        .then(text => console.log(text));
    },Promise.resolve())
}

分析一下,上邊的代碼,用fetch同時讀取一種url,每一個fetch操做都返回一個Promise對象,放入textPromise數組,而後reduce方法一次處理每一個Promise對象,而後用then鏈接起來,一次輸出結果。

缺點:這種方法看起來不太好理解,不太直觀,用async函數會更好一點。

async function readUrls(urls){
    for(const url of urls){
        const response = await fetch(url)
        console.log(response.text())
    }
}

能夠看到,代碼是大大簡化了,可是會有一個新的問題,就是必須等到前邊一個讀完了,纔會讀取下一個數據。

function readUrls(urls){
    const textPromise = urls.map(async url => {
        const response = await fetch(url)
        return response.text()
    })
}

//按照順序輸出
for(const text of textPromise){
    console.log(await text)
}

上邊的函數,雖然map方法的參數是async函數,但倒是併發執行的,由於內部是繼發執行,不影響外部。在後邊的循環中使用了await,這樣,仍是會依次輸出。

相關文章
相關標籤/搜索