重学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,所以实现了按顺序输出。

相关文章
相关标签/搜索