欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

使用ES2017的异步函数

程序员文章站 2024-03-18 10:34:16
...

ES2017在6月份已经定稿 ,随之而来的是我最喜欢的JavaScript特性将得到广泛的支持: async 函数。如果你以前使用JavaScript的异步函数遇到过困难,那这个就是为您准备的。如果你没有,那么,你可能是这方面的高手。http://www.tuicool.com/articles/VVNz2yA

异步函数或多或少让你编写有顺序的JavaScript代码,而不需要在回调(callbacks)、生成器(generators)或者是Promise中包装所有逻辑。考虑一下下面的代码:

function logger() {
    let data = fetch('http://sampleapi.com/posts')
    console.log(data)
}

logger()

这段代码不符合你的预期。如果你用JS做了什么,你可能知道为什么。

但是这段代码确实做到了你所期望的。

async function logger() {
    let data = await fetch('http:sampleapi.com/posts')
    console.log(data)
}

logger()

使用ES2017的异步函数

这种直观(而且漂亮)的代码可以正常工作,而且只需要另外两个单词!

ES6之前的JavaScript异步函数

在深入了解 async 和 await 之前,你先要理解Promise。为了领会Promise,我们需要再多走一步,回到普通的回调中。

在ES6中引入Promise,并在JavaScript中编写异步代码时取得了很大的进步。再没有地狱般回调(Callback hell),有点亲切感。

回调是一个函数,它可以传递到函数中,并在函数内调用,以响应任何事件。这是JS的基础。

function readFile('file.txt', (data) => {
    // 这在回调函数中
    console.log(data)
}

该函数只是从一个文件中记录数据,这在文件完成读取之前是不可能的。这似乎很简单,但是如果你想要按顺序读取和记录五个不同的文件,又会怎么样呢?

在Promise之前,为了按顺序执行任务,你需要在回调中嵌套回调,就像这样:

// This is officially callback hell
function combineFiles(file1, file2, file3, printFileCallBack) {
    let newFileText = ''
    readFile(string1, (text) => {
        newFileText += text
        readFile(string2, (text) => {
            newFileText += text
            readFile(string3, (text) => {
                newFileText += text
                printFileCallBack(newFileText)
            }
        }
    } 
}

很难推理,很难跟上。这甚至不包括对完全可能的场景的错误处理,比如其中一个文件不存在。

有关于JavaScript中回调地狱更多的资料,可以阅读下面的文章: - 回调地狱的今生前世 - Node.js 异步最佳实践 & 避免回调地狱 - “回调地狱”如何避免 - 理解回调函数,回调地狱,Promise - 关于回调地狱 - Callback Hell

Promise会让它变得更好

这是 Promise 可以提供帮助的地方。Promise是一种解释尚未存在的数据的方法,但你知道它会存在。《 您不知道的JS 》系列的作者@Kyle Simpson分享JavaScript异步函数而闻名。他对 Promise的解释 是:就像在快餐店里的食物一样。

  • 你食物的顺序
  • 给你的食物买单,并收到一张有订单号的票
  • 等待你的食物
  • 当你的食物准备好了,他们就会给你打电话
  • 收到的食物

正如他所指出的,当你在等待的时候,你可能不能吃你的食物,但你可以考虑一下,你可以为此做好准备。你可以继续做你想做的事情,你知道食物将会到来,即使你还没有食物,因为承诺会给你食物。这就是 promise 。表示最终会存在的数据的对象。

readFile(file1)
    .then((file1-data) => { /* do something */ })
    .then((previous-promise-data) => { /* do the next thing */ })
    .catch( /* handle errors */ )

这就是 promise 语法。它的主要好处是它允许一种直观的方式将连续事件链在一起。这个示例不错,但是你可以看到我们仍然在使用回调。 promise 只是简单的回调,只是让回调看上去更为直观一些。

最佳方式:async / await

几年前, async 函数进入了JavaScript生态系统。截至上个月,它是该语言的官方特性,并得到了广泛支持。

async 和 await 关键词是建立在Promise和generator上。本质上,它允许我们使用 await 关键词暂停我们想要的任何地方的函数。

async function logger() {
    // pause until fetch returns
    let data = await fetch('http://sampleapi.com/posts')
    console.log(data)
}

这段代码运行并做到你想要做的事情。它从API调用 data 。如果你的大脑没有爆炸,我不知道该如何取悦你。

它的好处是非常直观。你编写代码的方式就是你大脑思考的方式,告诉脚本在需要的地方暂停。

另一个好处是你可以在不能实现的 promise 中使用 try 和 catch :

async function logger ()  {
    try {
        let user_id = await fetch('/api/users/username')
        let posts = await fetch('/api/`${user_id}`')
        let object = JSON.parse(user.posts.toString())
        console.log(posts)
    } catch (error) {
        console.error('Error:', error) 
    }
}

这是一个人为的示例,但它证明了一点: catch 将获取过程中任何步骤中发生的错误。至少有三个地方的 try 块可能会失败,这是迄今为止在异步代码中处理错误的最干净的方法。

我们还可以使用带有循环和条件的 async 函数,而且不会令你感到头痛:

async function count() {
    let counter = 1
    for (let i = 0; i < 100; i++) {
        counter += 1
        console.log(counter)
        await sleep(1000)
    }
}

这是一个很愚蠢的例子,但它的执行将是你所期望的,而且代码还很容易阅读。如果你在控制台中执行此操作,你将看到代码将暂停 sleep 调用,下一个循环代码不会在一秒中内启动。

细节

既然你已经确信了 async 的美妙之处,那么就让我们深入了解一下细节吧:

  • async 和 await 是建立在Promise之上的。使用 async 的函数本身将始终返回一个Promise。记住这一点很重要,而且可能是你遇到的最大的“陷阱”
  • 当我们 await 时它会暂停函数,而不是整个代码
  • async 和 await 是非阻塞的
  • 你依旧可以使用 Promise 的助手,比如 Promise.all()

下面的代码是我们之前的例子:

async function logPosts ()  {
    try {
        let user_id = await fetch('/api/users/username')
        let post_ids = await fetch('/api/posts/<code>${user_id}')
        let promises = post_ids.map(post_id => {
            return  fetch('/api/posts/${post_id}')
        }
        let posts = await Promise.all(promises)
        console.log(posts)
    } catch (error) {
        console.error('Error:', error) 
    }
}
  • await 只能用于已被声明为 async 的函数
  • 不能在全局范围内使用 await

比如下面的代码:

// throws an error
function logger (callBack) {
    console.log(await callBack)
}

// works!
async function logger () {
    console.log(await callBack)
}

现在可以用

到2017年6月,几乎所有浏览器都可以使用 async 和 await 。更妙的是,要确保你的代码在任何地方都有效,请使用Babel来编译你的JavaScript代码,让更老的浏览器也能支持。

如果你对更多关于ES2017内容感兴趣,你可以浏览这个 ES2017特性的完整列表 。

本文根据 @ERIC WINDMILL 的《 Using ES2017 Async Functions 》所译,整个译文带有我们自己的理解与思想,如果译得不好或有不对之处还请同行朋友指点。如需转载此译文,需注明英文出处: https://css-tricks.com/using-es2017-async-functions/ 。

使用ES2017的异步函数

大漠

常用昵称“大漠”,W3CPlus创始人,目前就职于手淘。对HTML5、CSS3和Sass等前端脚本语言有非常深入的认识和丰富的实践经验,尤其专注对CSS3的研究,是国内最早研究和使用CSS3技术的一批人。CSS3、Sass和Drupal中国布道者。2014年出版《 图解CSS3:核心技术与案例实战 》。