您现在的位置是:首页 > 正文

重学ES6 async

2024-02-29 14:49:18阅读 0

含义

async函数,使得异步变得更方便。async函数是什么?一句话就是 Generator的语法糖。

用 Generator 依次读取两个文件

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 函数

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 就会先返回,等到异步完成,再接着执行函数体后面的语句。

指定多少毫秒输出一个值

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 对象。

async 内部 return 语句返回的值,会成为 then 方法回调的参数

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

f().then(v=>{
    console.log(v) // hello world
})
复制代码

async 函数内部抛出错误会导致返回的Promise对象变为 rejected 状态。抛出的错误对象 会被 catch方法回调函数接收到。

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,因此实现了按顺序输出。

转载于:https://juejin.im/post/5cfef20151882562ea197ad2

网站文章

  • MyBatis实现模糊查询

    MyBatis实现模糊查询

    今天我们来学习mybatis实现模糊查询1.第一种方式第一种方式:在java程序中,把like的内容组装好,把这个内容传入到sql语句我们先在dao接口中定义一个方法//like的第一种方式List<...

    2024-02-29 14:49:11
  • stm32f407时钟配置方法

    这里我们写一个RCC配置函数来说明各函数的用途,其中HSE = 8MHz。 /** * @说明 配置STM32F407的时钟系统 * @参数 无 * @返回 无 * @说明 void Clock_Config(void) 按如下表格配置时钟 * *===================================

    2024-02-29 14:48:43
  • activities工作流入门笔记-002-1-建表

    1.activities工作流入门(1).建表(使用默认配置文件,简化代码)  使用默认配置 配置文件名称:activiti-context.xml 或者activiti.cfg.xml放根目录。&l...

    2024-02-29 14:48:35
  • android源码学习——Bind学习梳理

    Bind学习参照以下路子走1、先会用掌握如何使用AIDL会写基于Binder的C/S架构服务2、主要流程了解注册服务、获取服务流程掌握启动、获取Service Manager3、回归应用,明白framework层Bind接口4、深入了解Bind驱动5、能够对各个主要流程画出流程图、甚至深入源码...

    2024-02-29 14:48:30
  • overridePendingTransition(0,0);8.0手机屏幕闪一下,黑屏一下

    经常在做搜索时一般都是新跳一个界面,但是又不想让用户感觉到,都会去掉activity的跳转动画,在8.0一下直接在跳转和finish()的地方加上overridePendingTransition(0...

    2024-02-29 14:48:02
  • Python 爬虫:Header 的设置

    Python 爬虫:Header 的设置

    在编写爬虫的过程中,有些网站会设置反爬机制,对不是来源于浏览器的访问进行拒绝,此时我们会收到 403 错误响应码,或者收到“抱歉,无法访问“等字眼,这就需要在爬虫程序中修改请求的 headers 伪装浏览器访问,从而绕开网站的反爬机制获取正确的页面。

    2024-02-29 14:47:54
  • 如何通过一条数字人三维动画宣传片,打造出数字文旅

    如何通过一条数字人三维动画宣传片,打造出数字文旅

    在文旅行业中,数字人三维动画宣传片可以提供差异化、个性化的视觉体验,让文旅通过数字人实现多渠道、多元化文旅宣传。

    2024-02-29 14:47:46
  • 关于 vue 中使用html2canvas 遇到报错( Element is not attached to a Document)

    关于 vue 中使用html2canvas 遇到报错( Element is not attached to a Document)

    2024-02-29 14:47:14
  • ubuntu配置usb vedio 设备挂载

    ubuntu配置usb vedio 设备挂载

    参考:https://www.corvin.cn/474.html

    2024-02-29 14:47:08
  • JavaScript 中在 function() {} 前面加感叹号的作用

    用js库过程中偶然发现他们代码中有很多类似于如下的情形: 1 !function ($) { 2 //do sth 3 }(window.jQuery); 于是就试了一下: 1 !functi

    2024-02-29 14:47:02