查看原文
其他

【第1033期】使用ES2017的Async功能

池盛星 前端早读课 2019-06-01

前言

观察那些已经做了很多季、很多期的综艺节目,要保持新鲜观众又爱看的真是一件需要花很多时间、精力去做的事情。

今日早读文章由@池盛星翻译分享。

正文从这开始~

ES2017于6月份定稿, 并带来了广受支持的我最喜欢的JavaScript功能: async功能! 如果你曾经被JavaScript的异步推理搞得焦头烂额, 那么这就是为您准备的. 如果没有, 那么恭喜, 你肯定是一个超级天才.

异步功能或多或少允许你编写顺序的JavaScript代码, 而不需要封装你的所有的逻辑到callbacks, generators或者promises. 看下面的代码:

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();

这段代码看起来非常的直观, 优雅. 关键是能按照我们的意图执行, 并且与前面一段代码相比, 仅仅增加了两个单词而已!

ES6之前的异步JavaScript

在我们深入async和await之前, 理解promises是很重要的前提. 而要搞明白promises, 我们要回到普通的callbacks.

Promises是在ES6中被引入的, 并且极大的改进了在JavaScript中的异步编程. 再也不需要去管大家平时所熟知的”回调地狱”.

callback是一个回调函数, 它可以被传递给一个函数并在该函数内部被调用, 作为对函数内部的任何事件的响应. 这是JavaScript 的基础 .

function readFile('file.txt', (data) => {    // This is inside the callback function    console.log(data); })

这个函数只是读取一个远程文件获取数据并在控制台打印输出, 在文件被完全下载到本地之前, 这是不可能的完成的. 它看起来很简单, 但是如果你想按顺序读取和打印5个不同的文件该怎么办?

在Promises被引入之前, 为了按顺序执行任务, 你不得不嵌套回调函数callbacks, 如下:

// This is officially callback hellfunction combineFiles(file1, file2, file3, printFileCallBack) {    let newFileText = '';    readFile(file1, (text) => {        newFileText += text;        readFile(file2, (text) => {            newFileText += text;            readFile(file3, (text) => {                newFileText += text;                printFileCallBack(newFileText);            });        });    }); })

这看起来让人头晕转向, 仿佛掉进了地狱. 这还不包括对于因为某个文件不存在而完全可能导致的请求错误的处理 .

Promise

Promise是一种对尚未返回的数据的一种承诺, 但是你知道它一定会返回的. Kyle Simpson, 你不知道的JS系列的作者, 因异步JavaScript演讲而闻名. 在他的演讲中, 他对promises的解释是: 就像从快餐店订购食物一样.

  • 下订单

  • 付款并且得到一张带订单号的小票

  • 等待您的食物

  • 当食物已经准备好, 他们会叫您的小票号码

  • 收到食物

他指出, 当您在等待您的食物的时候, 您可能不能尝到它, 但您可以想象它, 并准备享用它. 您可以等待一整天因为您知道食物即将到来, 即使您还没有得到它, 但是它已经被承诺给您了. 这就是Promise, 代表无论如何最终将存在的数据的对象.

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

这就是promise的语法. 它的主要好处是允许直观的链式调用连续事件. 这个简单的例子已经很好, 但是您会发现我们仍然在使用回调函数. Promises只是对callbacks的简单的封装, 使它更加直观.

最(新)好的方式: Async / Await

几年前, async功能已经进入了JavaScript生态系统. 但直到上个月(2017年6月), 才被JavaScript官方收录, 并获得广泛支持.

async和await是基于promises和generators的一个轻巧的封装. 本质上, 它允许在任何我们想要的地方使用await关键字来”暂停”我们的函数(功能).

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

这段代码能够按照我们的正常理解来按顺序执行. 它打印调用API返回的数据. 如果您还没有感到兴奋, 我也不知道该怎么办了.

这样做的好处是直观. 您可以跟着您的思路一直往下写代码, 告诉脚本在需要的地方暂停等待即可.

另一个极大的好处是, 您可以使用在promises中不能使用的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(posts.toString());        console.log(object);    } catch(error) {        console.error('Error: ', error);    } }

这是一个为了说明问题而人为构造的例子, 但它证明了一点: catch将捕获程序执行的过程中try里面任何步骤可能发生的错误. try代码块里面至少有三个可能执行失败的地方, 这是迄今为止, 在异步代码中捕获错误的最简洁清晰的方法.

我们还可以轻松的在循环和条件语句中使用async:

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

这个例子有点傻, 但是它可以按照您所期待的执行, 并且很容易读懂. 如果您在控制台执行这段代码, 您将发现, for循环里面每执行一次循环就会调用一次sleep, 暂停一秒进入下一个循环.

细节与要点

现在, 您已经被async和await的美所折服, 那么就让我们来更深入了解一下他们的细节:

  • async和await基于promises. 如果一个函数使用async, 那么它总是返回一个promise. 意识到这一点非常重要, 并且这很可能是一个将来您会掉进去的大坑.

  • 当我们await时, 它只暂停了这个函数, 而不是阻塞整个进程.

  • async和await是非阻塞的.

  • 您仍然可以使用像Promise.all()这样的Promise助手. 以下是我们稍早的例子结合使用promise:

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 新特性

【第1026期】ES8 新特性一览 .

浏览器支持情况

关于本文

译者:@池盛星

译文:https://zhuanlan.zhihu.com/p/28562434

作者:@ ERIC WINDMIL

原文:http://2ality.com/2016/02/ecmascript-2017.html

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存