关闭

如何优化async代码?更好的编写async异步函数

时间: 2018-05-14阅读: 1338标签: 异步

如何优化async代码,更充分的利用异步事件流 杜绝滥用async?


首先,你需要了解Promise

Promise是使用async/await的基础,所以你一定要先了解Promise是做什么的
Promise是帮助解决回调地狱的一个好东西,能够让异步流程变得更清晰。
一个简单的Error-first-callback转换为Promise的例子:

const fs = require('fs')

function readFile (fileName) {
  return new Promise((resolve, reject) => {
    fs.readFile(fileName, (err, data) => {
      if (err) reject(err)

      resolve(data)
    })
  })
}

readFile('test.log').then(data => {
  console.log('get data')
}, err => {
  console.error(err)
})

我们调用函数返回一个Promise的实例,在实例化的过程中进行文件的读取,当文件读取的回调触发式,进行Promise状态的变更,resolved或者rejected
状态的变更我们使用then来监听,第一个回调为resolve的处理,第二个回调为reject的处理。


async与Promise的关系

async函数相当于一个简写的返回Promise实例的函数,效果如下:

function getNumber () {
  return new Promise((resolve, reject) => {
    resolve(1)
  })
}
// =>
async function getNumber () {
  return 1
}

两者在使用上方式上完全一样,都可以在调用getNumber函数后使用then进行监听返回值。 以及与async对应的await语法的使用方式:

getNumber().then(data => {
  // got data
})
// =>
let data = await getNumber()

await的执行会获取表达式后边的Promise执行结果,相当于我们调用then获取回调结果一样。 P.S. 在async/await支持度还不是很高的时候,大家都会选择使用generator/yield结合着一些类似于co的库来实现类似的效果


async函数代码执行是同步的,结果返回是异步的

async函数总是会返回一个Promise的实例 这点儿很重要
所以说调用一个async函数时,可以理解为里边的代码都是处于new Promise中,所以是同步执行的
而最后return的操作,则相当于在Promise中调用resolve:

async function getNumber () {
  console.log('call getNumber()')

  return 1
}

getNumber().then(_ => console.log('resolved'))
console.log('done')

// 输出顺序:
// call getNumber()
// done
// resolved

 

Promise内部的Promise会被消化

也就是说,如果我们有如下的代码:

function getNumber () {
  return new Promise(resolve => {
    resolve(Promise.resolve(1))
  })
}

getNumber().then(data => console.log(data)) // 1

如果按照上边说的话,我们在then里边获取到的data应该是传入resolve中的值 ,也就是另一个Promise的实例。
但实际上,我们会直接获得返回值:1,也就是说,如果在Promise中返回一个Promise,实际上程序会帮我们执行这个Promise,并在内部的Promise状态改变时触发then之类的回调。
一个有意思的事情:

function getNumber () {
  return new Promise(resolve => {
    resolve(Promise.reject(new Error('Test')))
  })
}

getNumber().catch(err => console.error(err)) // Error: Test


如果我们在resolve中传入了一个reject,则我们在外部则可以直接使用catch监听到。这种方式经常用于在async函数中抛出异常 如何在async函数中抛出异常:

async function getNumber () {
  return Promise.reject(new Error('Test'))
}
try {
  let number = await getNumber()
} catch (e) {
  console.error(e)
}

 

一定不要忘了await关键字

如果忘记添加await关键字,代码层面并不会报错,但是我们接收到的返回值却是一个Promise

let number = getNumber()
console.log(number) // Promise

 

所以在使用时一定要切记await关键字

let number = await getNumber()
console.log(number) // 1

 

不是所有的地方都需要添加await

在代码的执行过程中,有时候,并不是所有的异步都要添加await的。 比如下边的对文件的操作:
我们假设fs所有的API都被我们转换为了Promise版本

async function writeFile () {
  let fd = await fs.open('test.log')
  fs.write(fd, 'hello')
  fs.write(fd, 'world')
  await fs.close(fd)
}

我们通过await打开一个文件,然后进行两次文件的写入。
但是注意了,在两次文件的写入操作前边,我们并没有添加await关键字。
因为这是多余的,我们只需要通知API,我要往这个文件里边写入一行文本,顺序自然会由fs来控制
然后我们在最后使用await来关闭这个文件。
因为如果我们上边在执行写入的过程还没有完成时,close的回调是不会触发的,
也就是说,回调的触发就意味着上边两步的write已经执行完成了。


合并多个不相干的async函数调用

如果我们现在要获取一个用户的头像和用户的详细信息(而这是两个接口 虽说一般情况下不太会出现

async function getUser () {
  let avatar = await getAvatar()
  let userInfo = await getUserInfo()

  return {
    avatar,
    userInfo
  }
}


这样的代码就造成了一个问题,我们获取用户信息的接口并不依赖于头像接口的返回值。
但是这样的代码却会在获取到头像以后才会去发送获取用户信息的请求。
所以我们对这种代码可以这样处理:

async function getUser () {
  let [avatar, userInfo] = await Promise.all([getAvatar(), getUserInfo()])

  return {
    avatar,
    userInfo
  }
}

这样的修改就会让getAvatar与getUserInfo内部的代码同时执行,同时发送两个请求,在外层通过包一层Promise.all来确保两者都返回结果。

让相互没有依赖关系的异步函数同时执行


一些循环中的注意事项

forEach

当我们调用这样的代码时:

async function getUsersInfo () {
  [1, 2, 3].forEach(async uid => {
    console.log(await getUserInfo(uid))
  })
}

function getuserInfo (uid) {
  return new Promise(resolve => {
    setTimeout(_ => resolve(uid), 1000)
  })
}

await getUsersInfo()

这样的执行好像并没有什么问题,我们也会得到1、2、3三条log的输出,
但是当我们在await getUsersInfo()下边再添加一条console.log('done')的话,就会发现:
我们会先得到done,然后才是三条uid的log,也就是说,getUsersInfo返回结果时,其实内部Promise并没有执行完。
这是因为forEach并不会关心回调函数的返回值是什么,它只是运行回调。


不要在普通的for、while循环中使用await

使用普通的for、while循环会导致程序变为串行:

for (let uid of [1, 2, 3]) {
  let result = await getUserInfo(uid)
}

这样的代码运行,会在拿到uid: 1的数据后才会去请求uid: 2的数据


关于这两种问题的解决方案:

目前最优的就是将其替换为map结合着Promise.all来实现:

await Promise.all([1, 2, 3].map(async uid => await getUserInfo(uid)))

 

这样的代码实现会同时实例化三个Promise,并请求getUserInfo

P.S. 草案中有一个await*,可以省去Promise.all

await* [1, 2, 3].map(async uid => await getUserInfo(uid))

 

P.S. 为什么在使用Generator+co时没有这个问题

在使用koa1.x的时候,我们直接写yield [].map是不会出现上述所说的串行问题的
看过co源码的小伙伴应该都明白,里边有这么两个函数(删除了其余不相关的代码):

function toPromise(obj) {
  if (Array.isArray(obj)) return arrayToPromise.call(this, obj);
  return obj;
}

function arrayToPromise(obj) {
  return Promise.all(obj.map(toPromise, this));
}

co是帮助我们添加了Promise.all的处理的(膜拜TJ大佬)。


总结

总结一下关于async函数编写的几个小提示:

  1. 使用return Promise.reject()在async函数中抛出异常
  2. 让相互之间没有依赖关系的异步函数同时执行
  3. 不要在循环的回调中/for、while循环中使用await,用map来代替它


参考资料

  1. async-function-tips

原文来源:https://segmentfault.com/a/1190000014836153,作者GitHub: jiasm   


站长推荐

1.云服务推荐: 国内主流云服务商,各类云产品的最新活动,优惠券领取。地址:阿里云腾讯云华为云

2.广告联盟: 整理了目前主流的广告联盟平台,如果你有流量,可以作为参考选择适合你的平台点击进入

链接: http://www.fly63.com/article/detial/762

关闭

ajax异步图片/文件上传的实现_利用FormData对象

在前后分离开发中,经常会遇到上传文件的情况,后端会有一个文件上传接口,通过ajax上传成功后返回图片地址url给前端。

NodeJs中的异步

这篇文章主要讨论NodeJs中的异步问题。使用NodeJs编写进程也一年多了,在公司实习的时候,公司两个项目的后台都是我负责使用NodeJs和Sails框架编写的。工作模式主要是我在服务器端提供RESTful接口

Node.js 中的异步生成器和异步迭代

生成器函数在 JavaScript 中的出现早于引入 async/await,这意味着在创建异步生成器(始终返回 Promise 且可以 await 的生成器)的同时,还引入了许多需要注意的事项。

async 的异步操作模式

我还记得以前执行异步操作需要在越来越深的回调地狱中使用回调的那些“好日子”。虽然回调地狱并没有完全成为过去,但是使用 Promise 来代替回调的嵌套已经显得简单多了。

ES6 Promise all 的实现

Promise 是ES6语法标准里的新定义,一般用来处理异步方法的同步执行。本文主要介绍Promise all的用法和实现。加入有以下场景:我有5个异步方法

ES6 Promise的执行顺序和异步性

我们知道Promise用来处理异步操作,异步操作的执行顺序是相对比较复杂的,如果再涉及到链式调用,则情况会变得更复杂。这篇文章会通过一个相对简单的代码示例来让大家对Promise的执行顺序和异步性有正确的理解

async和await是如何实现异步编程?

而为了在较低版本的浏览器或Node支持这种语法,其中一种解决方案是将其转化为Generator函数和Promise来实现。换句话说,任何的async和await实现的异步函数,都可以替换成Generator函数和Promise实现。

怎样取消 JavaScript 中的异步任务?

有时候执行异步任务可能是很困难的,尤其是在特定的编程语言不允许取消被错误启动或不再需要的操作时。幸运的是 JavaScript 提供了非常方便的功能来中止异步活动。在本文中,你可以学到如何创建可中止的函数。

深浅克隆和Promise异步编程

回调地狱:上一个回调函数中继续做事情,而且继续回调(在真实项目的AJAX请求中经常出现回调地狱)=>异步请求、不方便代码的维护,Promise的诞生就是为了解决异步请求中的回调地狱问题

javascript如何实现异步请求?

JS中通常实现浏览器和服务器的数据交互,那么异步操作是关键的一部分:通常情况下,浏览器想服务器发送请求,服务器相应请求返回数据,浏览器执行异步操作。

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!