重學ES6 async

含義

async函數,使得異步變得更方便。async函數是什麼?一句話就是 Generator的語法糖。javascript

用 Generator 依次讀取兩個文件java

var fs = require('fs')
var readFile = functiton(filename) {
    return new Promise(function (resolve,reject) {
        fs.readFile(fileName, function(error, data) {
            if(error) return reject(error)
            resolve(data)
        })
    })
}

var gen = function* (){
    var f1 = yield readFile('/1')
    var f2 = yield readFile('/2')
    console.log(f1.toString())
    console.log(f2.toString())
}
複製代碼

將上面寫成 async 函數promise

var asyncReadFile = async function(){
    var f1 = await readFile('/1')
    var f2 = await readFile('/2')
    console.log(f1.toString())
    console.log(f2.toString())
}
複製代碼

經過比較,async 函數就是將 Generator 函數的 * 替換成了 async ,將yield 題替換成 await。僅此而已。併發

用法

async 函數 返回一個Promise 對象,可使用then添加回調,當函數執行的時候,一旦遇到 await 就會先返回,等到異步完成,再接着執行函數體後面的語句。異步

指定多少毫秒輸出一個值async

function timeout(ms){
    return new Promise((resolve)=>{
        setTimeout(resolve,ms)
    })
}

async function asyncPrint(value,ms) {
    await timeout(ms)
    cosnole.log(value)
}
asyncPrint('hello world',50)
複製代碼

寫成這種方式也能夠函數

async function timeout(ms){
    await new Promise(resolve =>{
        setTimeout(resolve,ms)
    })
}

async function asyncPrint(value,ms){
    await timeout(ms)
    console.log(value)
}
複製代碼

async 函數的多種使用形式

//函數聲明
async function foo(){}

// 函數表達式
const foo = async function(){}

// 對象方法
let obj = {async foo() {}}
obj.foo.then()

// class 方法
class Storage {
    constructor(){
        this.catchPromise = caches.open('avatars')
    }
    
    async getAvatar(name) {
        cosnt catch = await this.catchPromise;
        return catch.match(`/avatars/${name}.jpg`)
    }
}

const storage = new Storage()
storage.getAvatar('jack').then(...)
複製代碼

語法

返回 Promise 對象

async 函數 返回一個 Promise 對象。fetch

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

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

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

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

Promise 對象狀態變化

async 函數返回Promise對象必須等到內部全部的 await 命令後邊Promise 對象執行完纔會發生狀態改變,除非遇到 return 或者 拋出錯誤。只有 async 函數內部全部的異步操做執行完,纔會執行 then 方法指定的回調函數。

await 命令

正常狀況下, await 以後應該是一個 Promise 對象。若是不是,會被轉化爲一個當即resolve 的Promise。

async function f(){
    return await 123;
}

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

await 後面的Promise 變爲 rejected,reject參數會被catch到

async function f(){
    await Promise.reject('error')
}
f()
.then(v => console.log(v))
.catch(e => console.log(e)) // error
複製代碼

只要有一個await語句後面的Promise變爲 reject,整個async函數都會中斷執行。 有時,咱們那但願前面一個異步操做失敗,也不要中斷後面的異步操做。這時能夠將第一個await 放在 try catch 結構裏面,這樣無論這個異步是否是成功,第二個都會執行。 或者 在await的後面Promise對象後添加一個 catch方法,處理前面可能出現的錯誤。

錯誤處理

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

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

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

若是有多個await,能夠統一放在 try catch 代碼塊中

async function f(){
    try {
        var val1 = await ...
        var val2 = await ...
        var val3 = await ...
        console.log('final',val3)
    } catch(e){
        console.log(e)
    }
    
}
複製代碼

使用 try catch 實現屢次重複嘗試

async function test(){
    let i 
    for(i = 0;i<3;++i){
        try{
            await ...
            break
        } catch(err){}
    }
    console.log(i) // 3
}
複製代碼

使用注意點

第一點:await命令後面的Promise對象的結果kennel rejected,因此,最好把await放在 try catch代碼塊中。

第二點:多個 await 命令後面的異步若是不存在 繼發關係,最好讓他們同時觸發。

let foo = await getFoo()
let bar = await getBar()
複製代碼

上面代碼中,getFoo 和 getBar 是兩個獨立的異步(互不依賴)被寫成繼發關係。這樣比較耗時,由於只有getFoo完成之後纔會執行 getBar,徹底可讓他們同時觸發。

// 寫法 1
let [foo,bar] = await Promise.all([getFoo(),getBar()])
// 寫法 2
let fooPromise = getFoo()
let barPromise = getBar()
let foo = await fooPromise
let bar = await barPromise
複製代碼

async 函數的實現原理

async 函數實現原理就是將Generator函數和自動執行器包裝在一個函數裏。

async function fn(){
    
}
// 等同於
function fn(args){
    return spawn(function* (){
        
    })
}
複製代碼

其餘異步處理方法的比較

async Promise Generator進行比較

假定某個DOM元素上面,部署了一系列動畫,前一個結束,後一個纔開始。若是有一個動畫出錯,就再也不執行,返回上一個成功執行的動畫返回值。

首先是 Promise 寫法

function chainAnimationsPromise(el,animations){
    // ret 保存上一個動畫返回值
    var ret = null
    
    // 新建一個Promise
    var p = new Promise.resolve()
    
    // 使用then添加全部動畫
    for(var anim of animations){
        p = p.then(function(val){
            ret = val
            return anim(elem)
        })
    }
    return p.catch(function(e){
        return ret
    })
}
複製代碼

代碼徹底是Promise的API,不容易看出語義

下面是Generator寫法

function chainAnimationsGenerator(elem, animations) {
    return spawn(function*(){
        var ret = null 
        try {
            for(var anim of animations){
                ret = yield anim(elem)
            }
        } catch(e){
            
        }
        return ret
    })
}
複製代碼

這個寫法的問題在於必須有一個任務運行器自動執行Generator函數,必須保證yield語句後面的表達式返回一個Promise。

最後是 async 寫法

async function chainAnimationAsync(elem,animations){
    var ret = null
    try {
        for(var anim of animations) {
            ret = await anim(elem)
        }
    }catch(e){
        
    }
    return rett
}
複製代碼

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

依次遠程讀取一組URL,按照讀取順序輸出結果

promise

function loginOrder(urls){
    // 遠程讀取全部url
    const textPromises = urls.map(url => {
        return fetch(url).then(res => res.text())
    })
    //按順序輸出
    textPromises.reduce((chain,textPromise) => {
        return chain.then(() => textPromise)
        .then(text => console.log(txt))
    },Promise.resolve())
}
複製代碼

async

async function loginOrder(urls){
    for(const url of urls){
        const response = await fetch(url)
        console.log(await response.text())
    }
}
複製代碼

以上寫法是繼發的,效率很低,咱們須要同時發出遠程請求。

async function loginOrder(urls){
    const textPromises = urls.map(async url => {
        const response = await fetch(url)
        return response.text()
    })
    
    for(const textPromise of textPromises) {
        console.log(await textPromise)
    }
}
複製代碼

以上代碼,雖然map的參數是async函數,可是,他是併發執行的,只有async內部是繼發執行,外部是不受影響的。後面的for of 內部使用了await,所以實現了按順序輸出。

相關文章
相關標籤/搜索