首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

使用JavaScript进行异步编程

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

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

序言

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

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

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

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

将文件内容写入另一个文件。

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

Promises 和 async/await

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

Promises

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

上有两个回调:和。

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

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

这里回调在 resolve时调用,回调在reject时调用。

另外对象有一些实用的静态方法如,,和。

方法可以将多个实例包装成一个新的实例,全部的都的时候返回的是一个结果数组,有任何都会使最后的结果变为。

就是赛跑的意思,意思就是说,里面哪个结果获得的快,就返回那个结果,不管结果本身是状态还是状态。

方法创建一个实例并调用方法处理给定参数。

方法创建一个实例并调用方法处理给定参数。

async/await

的目的是简化同时使用多个时的行为,避免了大量使用回调(而带来的回调地狱)。

正如类似于结构化回调,结合了和 的特点简化了异步程序的编写。

可以使用关键字将函数标记为异步函数。即:或。

函数返回的永远是一个对象。只要是函数的返回值,必然会被包含在对象中。

如果函数内部存在未捕捉到的异常,则通过的返回异常。

可以在函数内部返回对象的语句前使用 。这种情况下,函数的执行将被“暂停”,直到的语句执行完毕,并且返回值不再是一个对象而是其的返回结果。

只在方法内部的有效。

读写单个文件

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

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

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

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

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

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

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

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

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

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

使用的情况

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

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

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

使用的情况

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

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

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

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

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

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

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

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

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

使用并删除所有对的调用。

将这两个辅助函数放到它们自己的文件中。

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

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

读写多个文件

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

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

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

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

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

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

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

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

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

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

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

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

结论

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

我们可以与数组的方法一起使用来创建并同时处理它们。我们也可以在等待所有被完成之前使用运算符,即:

如果要在函数内部使用块,则需要在返回的任何值或函数之前使用运算符。

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

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20200108A0GXWC00?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券