[译]Async函数,让promise更友好!

Async 函数是一个非常了不起的东西,它将会在Chrome 55中得到默认支持。它允许你书写基于promise的代码,但它看起来就跟同步的代码一样,而且不会阻塞主线程。所以,它让你的异步代码看起来并没有那么"聪明"却更具有可读性。

Async 函数的代码示例:


  1. async function myFirstAsyncFunction() { 
  2.   try { 
  3.     const fulfilledValue = await promise; 
  4.   } 
  5.   catch (rejectedValue) { 
  6.     // … 
  7.   } 
  8. }  

如果你在一个函数声明的的前面使用async关键字,那你就可以在这个函数内使用await。当你去await一个promise的时候,这个函数将会以非阻塞的方式暂停,直到promise处于settled状态。如果这个Promise返回的是成功的状态,你将会得到返回值,如果返回的是失败的状态,那失败的信息将会被抛出。
提示: 如果你对promises不熟悉,请查看我们的promises指南

示例1: 打印响应信息

假设我们想要请求一个URL然后把响应信息打印出来,下面是使用promise的示例代码:


  1. function logFetch(url) { 
  2.   return fetch(url) 
  3.     .then(response => response.text()) 
  4.     .then(text => { 
  5.       console.log(text); 
  6.     }).catch(err => { 
  7.       console.error('fetch failed', err); 
  8.     }); 
  9. }  

下面用async 函数来实现同样的功能:


  1. async function logFetch(url) { 
  2.   try { 
  3.     const response = await fetch(url); 
  4.     console.log(await response.text()); 
  5.   } 
  6.   catch (err) { 
  7.     console.log('fetch failed', err); 
  8.   } 
  9. }  

可以看到代码行数和上例一样,但是使用async函数的方式使得所有的回调函数都不见了!这让我们的代码非常容易阅读,特别是那些对promise不是特别熟悉的同学。

提示: 你await的任何值都是通过Promise.resolve()来传递的,所以你可以安全地使用非本地的promise.

Async 函数的返回值

不管你是否在函数内部使用了await, Async 函数总是返回一个promise 。当
async函数显示滴返回任意值时,返回的promise将会调用resolve方法,
当async函数抛出异常错误时,返回的promise将会调用reject方法,所以:


  1. // wait ms milliseconds 
  2. function wait(ms) { 
  3.   return new Promise(r => setTimeout(r, ms)); 
  4.  
  5. async function hello() { 
  6.   await wait(500); 
  7.   return 'world'; 
  8. }  

当执行hello()时,返回一个成功状态,并且传递的值为world的promise.


  1. async function foo() { 
  2.   await wait(500); 
  3.   throw Error('bar'); 
  4. }  

当执行hello()时,返回一个失败状态,并且传递的值为Error('bar')的promise.

示例2: 响应流

在更复杂点的案例中, async函数更能体现其优越性。假设我们想要在记录chunks数据时将其变成响应流, 并返回最终的信息长度。

提示: "记录chunks" 让我感觉很别扭.

下面是使用promise的方式:


  1. function getResponseSize(url) { 
  2.   return fetch(url).then(response => { 
  3.     const reader = response.body.getReader(); 
  4.     let total = 0; 
  5.  
  6.     return reader.read().then(function processResult(result) { 
  7.       if (result.done) return total; 
  8.  
  9.       const value = result.value; 
  10.       total += value.length; 
  11.       console.log('Received chunk', value); 
  12.  
  13.       return reader.read().then(processResult); 
  14.     }) 
  15.   }); 
  16. }  

看清楚了,我是 promise “地下党” Jake Archibald。看到我是怎样在它内部调用 processResult
并建立异步循环的了吗?这样写让我觉得自己“很聪明”。但是正如大多数“聪明的”代码一样,你不得不盯着它看很久才能搞清楚它在做什么,就像九十年代的那些魔眼照片一样。引用

让我们用async函数来重写上面的功能:


  1. async function getResponseSize(url) { 
  2.   const response = await fetch(url); 
  3.   const reader = response.body.getReader(); 
  4.   let result = await reader.read(); 
  5.   let total = 0; 
  6.  
  7.   while (!result.done) { 
  8.     const value = result.value; 
  9.     total += value.length; 
  10.     console.log('Received chunk', value); 
  11.     // get the next result 
  12.     result = await reader.read(); 
  13.   } 
  14.  
  15.   return total; 
  16. }  

所有的"聪明"的代码都不见了。现在新的异步循环使用了可靠的,看起来普通的while循环来代替,这使我感觉非常的整洁。更多的是,在将来,我们将会使用async iterators,它将会使用for of循环来代替while循环,那这讲会变得更加整洁!

提示: 我对streams比较有好感。如果你对streams不太熟悉,可以看看我的指南

Async 函数的其他语法

我们已经看过了async function() {} 的使用方式,但是async关键字还可以用于其他的函数语法中。

箭头函数


  1. // map some URLs to json-promises 
  2. const jsonPromises = urls.map(async url => { 
  3.   const response = await fetch(url); 
  4.   return response.json(); 
  5. }); 

提示: array.map(func)不会在乎你给的是否是async函数,它只会把它当做一个返回值是promise的普通函数。所以,第二个回调的执行并不会等待第一个回调中的await处理完成。

对象方法


  1. const storage = { 
  2.   async getAvatar(name) { 
  3.     const cache = await caches.open('avatars'); 
  4.     return cache.match(`/avatars/${name}.jpg`); 
  5.   } 
  6. }; 
  7.  
  8. storage.getAvatar('jaffathecake').then(…);  

类方法


  1. class Storage { 
  2.   constructor() { 
  3.     this.cachePromise = caches.open('avatars'); 
  4.   } 
  5.  
  6.   async getAvatar(name) { 
  7.     const cache = await this.cachePromise; 
  8.     return cache.match(`/avatars/${name}.jpg`); 
  9.   } 
  10.  
  11. const storage = new Storage(); 
  12. storage.getAvatar('jaffathecake').then(…); 

提示: 类的 constructors和getters/settings不能是 async 函数。

注意!请避免太过强调顺序

尽管你正在写的代码看起来是同步的,但请确保你没有错失并行处理的机会。


  1. async function series() { 
  2.   await wait(500); 
  3.   await wait(500); 
  4.   return "done!"; 
  5. }  

上面的代码需要 1000ms才能完成,然而:


  1. async function parallel() { 
  2.  const wait1 = wait(500); 
  3.  const wait2 = wait(500); 
  4.  await wait1; 
  5.  await wait2; 
  6.  return "done!"; 
  7. }  

上面的代码只需要500ms,因为两个wait在同一时间处理了。

示例3: 顺序输出请求信息

假设我们想要获取一系列的URL响应信息,并将它们尽可能快的按正确的顺序打印出来。

深呼吸....下面就是使用promise来实现的代码:


  1. function logInOrder(urls) { 
  2.   // fetch all the URLs 
  3.   const textPromises = urls.map(url => { 
  4.     return fetch(url).then(response => response.text()); 
  5.   }); 
  6.  
  7.   // log them in order 
  8.   textPromises.reduce((chain, textPromise) => { 
  9.     return chain.then(() => textPromise) 
  10.       .then(text => console.log(text)); 
  11.   }, Promise.resolve()); 
  12. }  

Yeah, 这达到了目的。我正在用reduce来处理一串的promise,我太"聪明"了。这是一个如此"聪明"的代码,但我们最好不要这样做。

但是,当把上面的代码转换成使用 async函数来实现时,它看起来太有顺序了,以至于会使我们很迷惑:

:-1: 不推荐 - 过于强调先后顺序


  1. async function logInOrder(urls) { 
  2.   for (const url of urls) { 
  3.     const response = await fetch(url); 
  4.     console.log(await response.text()); 
  5.   } 
  6. }  

看起来整洁多了,但是我的第二个请求只有在第一个请求被完全处理完成之后才会发出去,以此类推。这个比上面那个promise的实例慢多了。幸好这还有一个中立的方案:

:+1: 推荐 - 很好而且并行


  1. async function logInOrder(urls) { 
  2.   // fetch all the URLs in parallel 
  3.   const textPromises = urls.map(async url => { 
  4.     const response = await fetch(url); 
  5.     return response.text(); 
  6.   }); 
  7.  
  8.   // log them in sequence 
  9.   for (const textPromise of textPromises) { 
  10.     console.log(await textPromise); 
  11.   } 
  12. }  

在这个例子中,全部的url一个接一个被请求和处理,但是那个'聪明的'的reduce被标准的,普通的和更具可读性的for loop 循环取代了。

浏览器兼容性和解决方法

在我写这篇文章时,Chrome 55已经默认支持async 函数。但是在所有主流浏览器中,它还在开发中:

  • Edge - In build 14342+ behind a flag
  • Firefox - active development
  • Safari - active development

解决方法 1:Generators

所有的主流浏览器的最新版本都支持generators,如果你正在使用它们,你可以稍稍polyfill一下 async函数.

Babel正可以为你做这些事情,这里有个通过Babel REPL写的示例 - 是不是感觉对转换后的代码很熟悉。这个转换机制是 Babel's es2017 preset的一部分。

提示: Babel REPL是一个很有趣的东西,试试吧。

我建议你现在就这样做,因为当你的目标浏览器支持了async函数时,你只需要将Babel从你的项目中去除即可。但是如果你真的不想使用转换工具,你可以使用Babel's polyfill点击预览。


  1. async function slowEcho(val) { 
  2.   await wait(1000); 
  3.   return val; 
  4. }  

当你使用了上面说的polyfill点击预览,你可以将上面的代码替换为:


  1. const slowEcho = createAsyncFunction(function*(val) { 
  2.   yield wait(1000); 
  3.   return val; 
  4. });  

注意到你通过给createAsyncFunction函数传递了一个generator (function*),然后使用yield 代替 await。除此之外它们的效果一样。

解决方法2: regenerator

如果你想要兼容旧的浏览器,Babel同样也能把generators给转换了,这样你就可以在IE8以上的浏览器中使用async函数,但你需要使用Babel的 es2017 preset和 the es2015 preset

你会看到转换后的代码并不好看,所以请小心代码膨胀。

Async all the things!

一旦所有浏览器都支持async函数了,请在所有返回值是promise的函数上使用async!因为它不仅可以使你的代码更tider, 而且它确保了async函数 总是返回一个 promise 。

回到 2014 年,我对async函数的出现感到非常激动, 现在很高兴看到它们在浏览器中被支持了。Whoop!

作者:kraaas

来源:51CTO

时间: 2024-08-30 14:05:25

[译]Async函数,让promise更友好!的相关文章

用Async函数简化异步代码

Promise 在 JavaScript 上发布之初就在互联网上流行了起来 - 它们帮开发人员摆脱了回调地狱,解决了在很多地方困扰 JavaScript 开发者的异步问题.但 Promises 也远非完美.它们一直请求回调,在一些复杂的问题上仍会有些杂乱和一些难以置信的冗余. 随着 ES6 的到来(现在被称作 ES2015),除了引入 Promise 的规范,不需要请求那些数不尽的库之外,我们还有了生成器.生成器可在函数内部停止执行,这意味着可把它们封装在一个多用途的函数中,我们可在代码移动到下

async 函数的含义和用法

本文是<深入掌握 ECMAScript 6 异步编程>系列文章的最后一篇. Generator函数的含义与用法 Thunk函数的含义与用法 co函数库的含义与用法 async函数的含义与用法 一.终极解决 异步操作是 JavaScript 编程的麻烦事,麻烦到一直有人提出各种各样的方案,试图解决这个问题. 从最早的回调函数,到 Promise 对象,再到 Generator 函数,每次都有所改进,但又让人觉得不彻底.它们都有额外的复杂性,都需要理解抽象的底层运行机制. 异步I/O不就是读取一个

Async/Await替代Promise的6个理由

译者按: Node.js的异步编程方式有效提高了应用性能;然而回调地狱却让人望而生畏,Promise让我们告别回调函数,写出更优雅的异步代码;在实践过程中,却发现Promise并不完美;技术进步是无止境的,这时,我们有了Async/Await. Node.js 7.6已经支持async/await了,如果你还没有试过,这篇博客将告诉你为什么要用它. Async/Await简介 对于从未听说过async/await的朋友,下面是简介: async/await是写异步代码的新方式,以前的方法有回调函

JavaScript 的 Async/Await 完胜 Promise 的六个理由

提醒一下各位,Node 现在从版本 7.6 开始就支持 async/await 了.如果你还没有试过它,这里有一堆带有示例的理由来说明为什么你应该马上采用它,并且再也不会回头. 貌似嵌入 gist 上的代码在 medium 原生 app 中不行,但是在移动浏览器上可以.如果你是在 app 中读本文,请点击共享图标,选择"在浏览器中打开",才看得到代码片段. Async/await 101 对于那些从未听说过这个话题的人来说,如下是一个简单的介绍: Async/await 是一种编写异步

[译]借助函数完成可组合的数据类型(软件编写)(第十部分)

本文讲的是[译]借助函数完成可组合的数据类型(软件编写)(第十部分), 原文地址:Composable Datatypes with Functions 原文作者:Eric Elliott 译文出自:掘金翻译计划 本文永久链接:github.com/xitu/gold-m- 译者:yoyoyohamapi 校对者:IridescentMia lampui Smoke Art Cubes to Smoke - MattysFlicks - (CC BY 2.0) (译注:该图是用 PS 将烟雾处理

设计分享总结:如何使分页做的更友好

文章描述:图录足迹,渐浅入深 今天小池提出一个问题讨论,如何使分页做的更友好.做了一些调研和思考,做了些总结. 分页在电商网站3级页.搜索结果页面等信息量大的页面是很重要的.我们在设计时要考虑: 分页的长度 首末页存在的必要 自定义页面的必要 设置场景去验证可用性. 1  用户模糊搜索一个关键字,往往会出现很多相关的结果.用户会逐页的去翻页去寻找,此时用户会使用"上一页.下一页"或附近的数字进行逐页检索.根据心智模型 ,保持用户点击的连贯性."上一页.下一页"距离相

改善DataGrid的默認分頁使其更友好

datagrid 改善DataGrid的默認分頁使其更友好         DataGrid是.net平台下開發Web應用程序最常用的控件,使用該控件可以幫助您專注與商務邏輯的開發,數據的顯示交給它來處理就好了,隻要簡單的設置一些相關的屬性,一張漂亮的表格就出來了,同時,它提供的模板列更大的提高了它的可編程性,使我們的設計更加靈活,想想都覺得這是一件興奮的事!然而,令人感覺美中不足的是,它的分頁功能實在是不怎麼樣,光禿禿的幾個數字掛在上面,既沒有統計信息又沒有跳頁功能,我想,這樣的用戶體驗太乏味

5个简单方法让网站对平板设备更友好

使用平板电脑的人越来越多,根据IDC的估计,2012年全球平板电脑的销售量就达到1.19亿台,但是专门为7-10寸屏幕设计的网站并不多.最近UX杂志在Mashable的刊登了一篇文章「 5简便的方法来您的网站的Tablet 」,卢松松编译如下: 在美国,有将近7000万的平板电脑用户,是2011年的两倍.这个国家有将近30%的网络流量来自平板电脑,2011年至2012年间电子商务网站来自平板电脑的流量甚至成长了. 方法都非常简单,见过简单的CSS修改,就可以让网站对平板用户更友好了. 什么是点击

[译] 为企业应用设计更好的表格

本文讲的是[译] 为企业应用设计更好的表格, 原文地址:Designing better tables for enterprise applications 原文作者:Adhithya 译文出自:掘金翻译计划 本文永久链接:github.com/xitu/gold-m- 译者:Lai 校对者:LeviDing Ruixi 为企业应用设计更好的表格 深入了解如何在企业应用中设计表格,以及如何避免常见的错误 企业应用通常很复杂,因为要展示大量的包含多种来源.模式和用户信息的数据.需要先浏览一下复杂