毫无疑问,虽然JavaScript的历史比较悠久,但这并不妨碍它成为当今最受欢迎的编程语言之一。对刚接触该语言的人来说,JavaScript的异步特性可能会有一些挑战。在本文中,我们将了解和使用Promiseasync/await来编写小型异步程序。通过这些示例,你将了解一些可以在自己程序中使用的异步技巧。

本文中的所有代码示例都是基于Node环境编写的,因此建议安装Node以后运行。虽然所有程序都是为Node编写的,但类似的语法在浏览器中也能同样运行,它们的异步编程的写法和原理是通用的。

序言

不管你是否相信JavaScript是一门真正的编程语言,事实是它现在非常的流行。如果你是Web开发人员,你就更应该花一些时间来学习它的优缺点。

JavaScript是单线程的,并且相当于是非阻塞异步流。如果是刚开始使用JavaScript进行异步编程,那么在调试异步代码时,可能会产生很多烦恼。相比常见的同步编程,异步编程需要更多的耐心和不同的思维方式。

在同步模式中,所有操作都发生在一个队列(或者中,更易于对程序进行推理;但是在异步模式中,操作可以在任何时间点以任何顺序开始或结束,每个函数执行结束的时间是不可预测。因此,仅仅依靠运行的顺序序列是不够的。异步编程需要在程序流程和设计方面进行更多思考。

在本文中,我们会尝试几个简单的异步程序,从简单到复杂。我们将编写实现这两个场景功能:

  • 将文件内容写入另一个文件。
  • 将多个文件的内容写入新文件。

Promises 和 async/await

让我们花点时间快速回顾一下promise和async / await的基础知识。

Promises

  • Promise是代表异步操作结果的对象。

  • Promise上有两个回调:resolve(成功之后的回调函数)reject(失败后的回调函数)

  • 一般而言,resolve的结果可以通过then获取。而reject的结果可以通过catch来获取。

  • 可以通过new关键字来使用Promise构造函数创建Promise。例如:

    const p = new Promise((r, j) => {});

  • 这里r回调在 resolve时调用,j回调在reject时调用。

另外Promise对象有一些实用的静态方法如allraceresolvereject

  • all方法可以将多个Promise实例包装成一个新的Promise实例,全部的Promiseresolve的时候返回的是一个结果数组,有任何reject都会使最后的结果变为reject
  • race就是赛跑的意思,意思就是说,Promise.race([p1, p2, p3])里面哪个结果获得的快,就返回那个结果,不管结果本身是resolve状态还是reject状态。
  • resolve方法创建一个Promise实例并调用resolve方法处理给定参数。
  • reject方法创建一个Promise实例并调用reject方法处理给定参数。

async/await

  • async/await的目的是简化同时使用多个Promise时的行为,避免了大量使用回调(而带来的回调地狱)。
  • 正如Promise类似于结构化回调,async/await结合了生成器Promise的特点简化了异步程序的编写。
  • 可以使用async关键字将函数标记为异步函数。即:async function hello() {}const hello = async() => {};
  • async函数返回的永远是一个Promise对象。只要是async函数的返回值,必然会被包含在Promise对象中。
  • 如果async函数内部存在未捕捉到的异常,则通过Promisereject返回异常。
  • 可以在async函数内部返回Promise对象的语句前使用await 。这种情况下,函数的执行将被“暂停”,直到awaitPromise语句执行完毕,并且返回值不再是一个Promise对象而是其resolve的返回结果。
  • await只在async方法内部的有效。

读写单个文件

本节中,我们将编写一个脚本来读取单个文件的内容,并将其写入一个新文件。

首先,我们将为程序的入口创建一个async方法:

async 

然后,我们需要创建两个Promise,一个代表文件的内容,另一个代表将内容写入另一个文件的操作结果:

async  

在上面的代码段中,readFilewriteFile都是异步的,并且都返回一个Promise。因此,需要使用await来确保readFile有返回值,以便在writeFile中使用它:

async  

最后,可以考虑一下要在main函数中返回什么。在这里,我们打算返回要写入的新文件的名称。要注意的是,返回值将被自动包装在Promise对象中。但是我们需要使用await来确保在函数执行完之前得到了writeFile的结果:

async  

现在,我们可以调用main函数并将结果或任何未捕获的异常打印出来:

main()  .then(r => console.log("Result:", r))  .catch(err => console.log("An error occurred", err);

为了使程序更加完整,我们需要使用fs模块并将fs.readFilefs.writeFilePromise化,即promisify。完整的脚本如下所示:

const util = 

在上面的代码段中,我们Promise化了fs.writeFilefs.readFile。promisify函数可以将任何遵循Node.js回调风格的函数,转换为基于Promise的函数。

接下来我们聊聊异常处理。你可以通过好几种方法来处理异常,具体取决于你想处理到什么程度。例如,在上面的代码片段中,我们在catch块里基本上捕获了main函数中可能发生的任何异常。不过它只在async方法内有用,未捕获的异常会通过该函数的reject返回。

但是,假设你想做更多的控制,并且希望根据每个async方法的错误来做不同的操作。在这种情况下,你可以在每个异步操作中使用try-catchcatch

使用try-catch的情况

我们先来看一下用try-catch的情况。

async  

在上面的代码段中,我们添加了两个try-catch块。另外,我们在第一个程序块try-catch之外创建了fileContent变量,以便在整个main函数中可用。注意,在每个try-catch中,如果出现异常,我们返回的是一个对象。错误对象包含一个消息字段和错误的详细信息。如果发生任何错误,函数会立即返回我们自定义的错误对象。请记住,返回的对象会被自动包含在Promise中。我们可以像以前一样调用main函数,不过这次可以在then()中检查错误对象:

main()  .then(r => {if(r.error) {return  console.log("An error occurred, recover here. Details:", r);    }return  console.log("Done, no error. Result:", r);  })  .catch(err =>  console.log("An error occurred", err));

注意,在then()中,我们会检查resolve对象是否存在错误。如果有,那么我们在这里进行错误处理;否则,我们只需将结果打印到日志。另一个catch块将捕获运行时错误或程序未处理的其他错误。

使用catch的情况

除了try-catch,我们也可以通过给每个Promise绑定一个catch来处理异常:

async  

这里你可能注意到了,我们给每个Promise 都加了catch方法,并返回一个自定义错误对象,类似于前面的示例。如果其中一个步骤有错误,将只返回这一步的结果,该结果仅包含我们的自定义错误对象。

但是,对于第二个操作,如果写操作成功,我们将明确地返回一个空对象。这是因为writeFile操作成功时传递给resolve的是undefined,而我们无法访问undefined值的error字段。所以如果写入成功,我们要返回一个resolve空对象的Promise

我们还可以写两个辅助函数,减少一些重复代码:

const call = 

call函数接受一个Promise,并返回一个Promise。如果结果为null或未定义,Promise将使用空对象进行处理;或者是操作的结果。如果有错误,将解析为一个包含错误信息的error对象。

error辅助函数需要result和msg两个参数,它将返回包含错误结果和自定义消息的对象。

添加这两个函数后,我们可以更新main函数:

async  

这里,我们将每个操作传递给call函数。然后检查是否有错误,如果有,那么只需调用error函数以返回带有自定义错误消息的自定义错误。完整的代码如下所示:

const util = 

为了更多地减少重复代码并使它变得更加模块化,我们还可以做两件事:

  • 使用fs-extra并删除所有对util.promisify的调用。
  • 将这两个辅助函数放到它们自己的文件中。

之后,我们将得到以下内容:

const fs = 

注意,由于我们正在使用fs-extra,如果不将回调传递给方法,则该函数默认将返回一个Promise。这就是为什么我们删除所有promisify调用,并直接在fs变量上转换所有fs调用的原因。另外,我们将两个辅助函数放到了他们自己的call.js文件中。

读写多个文件

在本节中,我们将编写一个脚本,该脚本读取多个文件的内容并将结果写入新文件。此示例的设置与上一节非常相似:

const fs = 

在上面的代码段中,首先我们需要fs-extra具有所有基于Promise的方法版本的模块fs。然后,我们将main async函数定义为程序的入口点。我们还定义了一个数组,其中包含要读取的文件的硬编码路径。

接下来,我们将编写一个遍历文件路径的for循环,并读取每个文件的内容:

const fs = 

在A行上,我们定义了for循环。在B行await上,我们根据的结果,fs.readFile并将其分配给content变量。最后,在C行中,我们将内容记录到控制台。让我们用实际的写文件操作替换log语句:

const fs = 

在上面的代码段中,我们首先在A行中定义文件的路径。然后在B行中,将结果写入新路径,并确保await在其上也是如此。我们需要在await这里,因为我们要确保在移至下一个文件之前完成写入。最后在C行,我们返回输入文件路径。

现在,上面的实现还可以,但是我们可以做得更好。在上面的实现中,我们一次处理一个文件。也就是说,我们等待每个文件的读写操作完成,然后再移动到下一个文件。实际上,我们可以通过创建一个Promise数组并发地运行每个读写过程,其中的每个Promise表示对文件的读写操作。最后,我们可以用来Promise.all(Promise[])方法同时处理所有Promise

const fs = 

在上面的代码段中,我们在A行上定义了一个数组来保存读写Promise。在行B上,我们开始遍历每个文件路径的for循环。在C行上,我们将自调用async函数推入readWrites数组。在每个async函数的主体内,我们读取每个文件的内容并写入一个新文件。在F行上,我们返回的结果fs.writeFile是一个Promise对象。最后,在G行中,我们用于Promise.all同时处理所有Promise。我们还await对结果进行解析,该结果解析为保存写入结果的单个数组。如果写操作成功,我们应该得到一个未定义值的数组。这是因为write方法解析为undefined没有发生错误。

即使上面的实现完成了工作,我们也可以做得更好。我们可以在files数组上使用带有async函数的map方法,而无需使用自调用async函数。它也将更容易理解:

const fs = 

在上面的代码的A行中我们对files数组执行map方法,把它传递给一个async函数。在async函数内部,我们仅执行读写操作。最后在D行,我们调用Promise.all并传递readWrites数组。该readWrites数组保存了多个Promise,其中每个Promise代表每次读取和写入的结果。

现在,让我们扩展上面的示例。让我们创建一个文件夹,并将所有新文件放入其中。async在进行读写操作之前,我们将需要创建一个函数来为我们创建输出文件夹:

async  

在上面的代码段中,我们首先创建一个async名为的函数prepare。在A行,首先,output如果文件夹已经存在,则将其删除。我们还等待Promise完成,然后再移至B行。在B行上,我们创建了output文件夹,我们也等待完成。现在,在开始读写操作之前,我们可以在prepare函数内部使用该函数main

const fs = 

在A行上,我们等待prepare函数完成,然后再进行读写操作。我们还在行B上更新了输出文件路径。脚本的其余部分几乎相同。我们还将filesand output变量移到了main函数之外。如果运行上面的脚本,应该会看到一个output包含每个输入文件副本的文件夹。

结论

JavaScript从诞生到现在,已经演化为一个非常先进易用的语言,并且Promise以及async/await使异步程序变得更易写也更易读。现在我们已经到了文章的结尾,让我们回顾一些其它的要点:

  • 我们可以Promise.all与数组的map方法一起使用来创建Promise并同时处理它们。我们也可以在Promise.all等待所有Promise被完成之前使用await运算符,即:await Promise.all(inputs.map(async v => {}));
  • 如果要在async函数内部使用try-catch块,则需要在返回Promise的任何Promise值或函数之前使用await运算符。

JavaScript是一个功能强大的全栈语言,不仅可以开发Web前端,也使用Node.js开发后端,使用Electron开发桌面应用。同时也可以结合CukeTest、LeanRunner等工具开发自动化测试及RPA,应了那句老话"学好JavaScript,走遍天下都不怕"。学好异步编程是掌握JavaScript的关键,希望这篇文章对你有所帮助。

java 异步得到函数返回值_使用JavaScript进行异步编程相关推荐

  1. async js 返回值_获取JavaScript异步函数的返回值

    今天研究一个小问题: 怎么拿到JavaScript异步函数的返回值? 1.错误尝试 当年未入行时,我的最初尝试: function getSomething() { var r = 0; setTim ...

  2. java的main函数返回值_Java中的main方法

    首先需要说明的是: 1.main函数(主函数)是可以调用的,这种调用是没有意义的: 2. main函数只能出现在公共类中也就是public class中: 但我不明白的是:在eclipse中默认的in ...

  3. void函数返回值_(*void(*)()0)() 是什么

    (*void(*)()0)() 代码分析 这是啥 这行代码,是我今天在看<C陷阱与缺陷>时看到的,一开始很不能理解.慢慢上网摸索一些后,大致理解了,现在来分享一下我所理解的这行代码. 1. ...

  4. ostream作为函数返回值_函数的调用(一)

    函数作为计算机代码的一种抽象方式,它的作用不言而喻! 原文链接 认识函数: 定义:函数是一段代码的表示,是一段具有特定功能的,可重用的语句组 函数是一种功能的抽象,一般函数表达特定功能 两个作用:降低 ...

  5. python自定义函数返回值_第八讲 python自定义函数返回值

    注意自定义函数:统一文件内函数名称不能相同. 单返回值 语法结构: def function_name([para1,para2...]): code... code... ... return va ...

  6. python sort函数返回值_如何使用python sort函数?

    不知道大家在做项目时候,有没有遇到这个函数,记得小编第一次看到这个函数,一直纳闷这个函数的使用方法,而后查询了下,今日,小编再一次遇到这个函数,于是,就做了一番整理,内容请看下文. 与sort()函数 ...

  7. python sendto函数返回值_有返回值的函数amp;闭包(python)

    学习中...... 看了一段代码,有的地方还不是很理解,先贴一下 def count():fs = []for i in range(1, 4):def f():return i*ifs.append ...

  8. linux c read函数返回值,Linuxc - GNU Readline 库及编程简介

    GNU Readline 库及编程简介 简介 用过 Bash 命令行的一定知道,Bash 有几个特性: TAB 键可以用来命令补全 ↑ 或 ↓ 键可以用来快速输入历史命令 还有一些交互式行编辑快捷键: ...

  9. beautifulsoup find函数返回值_再端一碗BeautifulSoup

    在上一章我们介绍了如何使用BeautifulSoup抓取安徒生童话故事<丑小鸭>,通过一个简单的例子,大家应该对于python如何进行爬取网页内容有了一个初步的认识.在这一章节,我们将延续 ...

最新文章

  1. 字符串创建XML文档
  2. 创建MYSQL的储存过程
  3. JZOJ 5677. 【GDOI2018Day2模拟4.21】纽约
  4. notepad++ vim插件_是时候摒弃掉Notepad++ ,因为你还有更多的选择...
  5. 使用php自动将文章标题或内容进行分词,添加和删除标签功能实例!
  6. 日期条控件 DateFieldControl
  7. C++模板类与Java泛型类
  8. 【BZOJ4547】【HDU5171】小奇的集合,暴力+矩阵乘法
  9. JavaScript中的类方法、对象方法、原型方法
  10. hashMap的快速存取
  11. VS2010 学习版序列号(转)
  12. c++配合Cheat Engine实现cs1.6外挂
  13. 周云的FLASH小游戏开发教室_基础篇
  14. 微信公众号errcode大全
  15. 关于Office365邮箱附件大小限制问题
  16. Android-使用SoundPool实现语音计算器
  17. java web前端哪个城市,Java Web 是前端还是后端
  18. (八)空气质量指数计算7.0-----beautifulsoup4解析处理html、获取所有城市空气质量
  19. 中国航信借助NetApp存储系统打造高效数据中心
  20. 年终汇报工作,如何用项目管理工具展现成果

热门文章

  1. UC浏览器电脑版怎么恢复被关闭的网页
  2. Win11系统如何设置任务栏新消息提醒
  3. Android平台实现Unity3D下RTMP推送
  4. 学习Spring Boot:(二十五)使用 Redis 实现数据缓存
  5. Linux环境下安装Mysql5.7
  6. python高阶函数教学_Python 简明教程 --- 16,Python 高阶函数
  7. 浅谈python使用多态跟不用多态的区别_python 多态和 super 用法
  8. 眉骨高者为大贵之相_男人此处“高大”,大富大贵,前途不可限量!!
  9. 科研工作者结合实验与计算机模拟,理论物理前沿重点实验室
  10. 亚马逊产品描述计算机语言编辑,亚马逊Listing产品描述编辑讲解