资讯专栏INFORMATION COLUMN

ES6&ES7中的异步之async函数

dongxiawu / 1080人阅读

摘要:更好的语义和分别表示异步和等待,比起和更容易理解。前边声明关键字,表示内部有内部操作,调用函数会返回一个对象。等价于其中函数就是自动执行器。

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函数的改进,具体体现在四点

1.内置执行器:

相对于Generator函数需要co模块或者next()方法来作为执行器执行,async函数自带执行器,所以上边的代码只需要一句

_readFile()   

可以执行。

2.更好的语义:

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

3.更广泛的适用性:
yield后边只能跟Thunk函数或者Promise对象,但是在async函数中,可以跟Promise对象和基本数据类型。

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,这样,还是会依次输出。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/89737.html

相关文章

  • ES6&ES7中的异步Generator函数异步编程

    摘要:传统的异步方法回调函数事件监听发布订阅之前写过一篇关于的文章,里边写过关于异步的一些概念。内部函数就是的回调函数,函数首先把函数的指针指向函数的下一步方法,如果没有,就把函数传给函数属性,否则直接退出。 Generator函数与异步编程 因为js是单线程语言,所以需要异步编程的存在,要不效率太低会卡死。 传统的异步方法 回调函数 事件监听 发布/订阅 Promise 之前写过一篇关...

    venmos 评论0 收藏0
  • ES6-7

    摘要:的翻译文档由的维护很多人说,阮老师已经有一本关于的书了入门,觉得看看这本书就足够了。前端的异步解决方案之和异步编程模式在前端开发过程中,显得越来越重要。为了让编程更美好,我们就需要引入来降低异步编程的复杂性。 JavaScript Promise 迷你书(中文版) 超详细介绍promise的gitbook,看完再不会promise...... 本书的目的是以目前还在制定中的ECMASc...

    mudiyouyou 评论0 收藏0
  • Promise & Generator——幸福地用同步方法写异步JavaScript

    摘要:在这里看尤雨溪大神的这篇小短文,非常精简扼要地介绍了当前常用的。根据尤雨溪大神的说法,的也只是的语法糖而已。对象有三种状态,,。对象通过和方法来规定异步结束之后的操作正确处理函数错误处理函数。方便进行后续的成功处理或者错误处理。 最近在写一个自己的网站的时候(可以观摩一下~Colors),在无意识中用callback写了一段嵌套了5重回调函数的可怕的代码。回过神来的时候被自己吓了一跳,...

    Harpsichord1207 评论0 收藏0
  • 翻译:Taming the asynchronous beast with ES7

    摘要:让我们使用它从数组中返回一个值数组在中,我们可以这样做,这是一种更简单的方法最重要的部分是创建数组,该数组立即调用所有的我们在主函数中等待这些。所以在我们真正等待完成之前,主函数就退出了。 原文:https://pouchdb.com/2015/03/0... PouchDB最棘手的方面之一是它的API是异步的。在Stack Overflow、Github和IRC上,我看到了不少困惑的...

    Eastboat 评论0 收藏0
  • ES6&ES7中的异步Generator的语法

    摘要:第二次同理,遇到了第二个函数会停下来,输出的遍历器对象值为,的值依然是。比如返回的遍历器对象,都会有一个方法,这个方法挂在原型上。这三个函数共同的作用是让函数恢复执行。 Generator的语法 generator的英文意思是生成器 简介 关于Generator函数,我们可以理解成是一个状态机,里面封装了多种不同的状态。 function* gener(){ yield hel...

    djfml 评论0 收藏0

发表评论

0条评论

dongxiawu

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<