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

80242f9b21d89266d4632b9f5dcde158.png

毫无疑问,虽然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的关键,希望这篇文章对你有所帮助。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/564809.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ARMA模型的性质之MA模型

目录 一、MA模型的定义 二、MA模型的统计性质 1.常数均值 2.常数方差 3.自协方差函数q阶结尾 4.自相关系数q阶截尾 举例: 三、MA模型的可逆 1.可逆的定义和条件 2.MA与AR模型的对比 3.逆函数的递推公式 举例: 四、MA模型的偏自相关系数拖尾…

ARMA模型的性质之ARMA模型

目录 一、ARMA模型的定义 二、平稳条件与可逆条件 三、传递形式与逆转形式 四、ARMA(p,q)模型的统计性质 1.均值 2.自协方差函数 3.自相关系数 4.ARMA(p,q)模型自相关系数拖尾,偏自相关系数拖尾 小结 一、ARMA模型的定义 具有如下结构的模型称为自回归移动…

R之Excel文件读取与程序包的安装调用

目录 方法一 方法二 1.用命令安装 2.从下拉菜单安装 三、加载所需安装包 方法一 方法二 四、使用新程序包读取数据 方法一 另存为 .csv 文件 这是wps的另存为 然后选择位置,重命名或更改格式为 .csv 这是excel 的另存为 文件 —— 另存为 —— 选择位置 …

diag开关什么意思_双控开关接线图_一灯双控开关接线图_单联双控开关接线图_双控开关接线图实物图...

电工学习网:www.diangon.com关注电工学习网官方微信公众号“电工电气学习”,收获更多经验知识。双控开关接线图_一灯双控开关接线图_单联双控开关接线图_双控开关接线图实物图现在市场上面所出售的开关种类非常的多,双控开关正好能够满足人们…

Office 安装MathType7.4 未找到MathPage.wll等问题

目录 问题描述: 解决方法: 问题描述: MathType v7.4 简体中文版是一款功能很强大的数学公式编辑器,在很多地方都会用的,而wps就不能适应各种场合。 但是该软件总会因为不明原因,有时会出现找不到MathType.dll或者MathPage.wll文…

python导入excel加入折线图_利用python向excel文件写数据并绘制折线图

依赖 python 2.7.15 xlswriter(可以使用pip insall xlswriter) 具体实现 #!/usr/bin/env python # -*- coding:utf-8 -*- import xlsxwriter # Create a workbook and add a worksheet. workbook xlsxwriter.Workbook(Expenses01.xlsx) worksheet work…

平稳序列的预测和拟合之单位根检验

目录 1.建模步骤 2.单位根检验 2.1 DF检验(以AR(1)模型为例) DF检验的等价表达 DF检验的三种类型 R语言单位根检验: 2.2 ADF检验 ADF检验的三种类型 小结 1.建模步骤 2.单位根检验 对平稳序列建模,首先要确定序列是平稳的…

如何自己去写一个鼠标驱动_为什么要用哈密顿采样器(Hamiltonian Monte Carlo),以及如何自己写一个...

背景介绍:(了解采样的可以跳过)1)为什么需要采样:简单的分布,比如高斯、exponential、gamma等等的样本都可以直接用numpy.random生成,但复杂的分布需要采样器生成。在贝叶斯、概率编程里面&…

java inputstream read_20191209-java部分流处理

流:流一般分为输入流(InputStream)和输出流(OutputStream)两类.但这种划分并不是绝对的.在Java开发环境中,主要是由包http://java.io中提供的一系列的类和接口来实现输入和输出处理.标准输入和输出处理则是由包java.lang中提供的类来处理的,但这些类又都是从包http://java.io中…

平稳序列的预测和拟合之模型识别

目录 1.计算样本相关系数和偏自相关系数 2.模型识别 模型定阶的困难 样本相关系数的近似分布及模型定阶经验方法 例题: 2.参数估计 常用估计方法: 1.矩估计 2.极大似然估计 3.最小二乘估计 R中,参数估计用arima函数 例题 小结 1.计算…

python自增_Python的自增运算与Python变量的浅析

一、关于Python的自增运算 学了C/C后再学习Python,不自觉地就打出了自增运算符,但是发现Python解释器不认识,查了下资料,发现Python中没有这个运算符。这里暂时不探讨自增运算符的内部实现原理,从语言设计角度来说&…

平稳序列的预测和拟合之模型检验

目录 1.模型的显著性检验 R语言实现 例题 2.参数显著性检验 例题 小结 1.模型的显著性检验 检验模型的有效性(对信息的提取是否充分) 判定原则: 一个好的拟合模型应该能够提取几乎所有的样本相关信息,即残差序列应该为白噪声序列。反之…

oracle数据如何获取游标中动态字段_原来Python自带了数据库,用起来真方便!

Python大数据分析记录 分享 成长Python作为数据科学主流语言,被广泛用于数据读存、处理、分析、建模,可以说是无所不能。数据一般存放在本地文件或者数据库里,之前介绍过如何使用python读取本地文件,也对# PyMySQL、cx_Oracle…

平稳序列的预测和拟合之模型优化

目录 前提 准则 1、AIC准则 2、SBC &#xff08;BIC)准则 优化 小结 前提 问题提出:模型通过检验&#xff0c;说明是有效的&#xff0c;但有效的模型不唯一。 下面我们用一个例子来解释一下&#xff1a; 例4-7:试对某次化学反应的70个过程数据序列进行拟合。 d<-r…

css中如何实现帧布局_浅谈web前端中的表格布局与CSS盒子布局

在web前端设计排版时我们可能会用到表格布局和divCSS布局&#xff0c;但现在主要使用后者&#xff0c;为何&#xff1f;今天我们来谈一谈两者之间的发展和原理。话不多说下面来干货发展过程上个世纪Web开发人员流行使用表格进行文档整体布局。因为当时大部分浏览器不支持CSS&am…

油猴的简介和安装

目录 1.油猴简介 2.油猴插件安装 方法1 方法2 3.获取油猴脚本 4.脚本的使用 4.1 脚本的设置及功能 4.2 安装油猴脚本 4.3 新建脚本 5.脚本编写方法 功能注释 脚本权限 编写脚本 1.油猴简介 油猴脚本是一款免费的浏览器扩展和最为流行的用户脚本管理器&#xff0c…

Logistic回归——二分类 —— matlab

目录 1.简介 2.应用范围 3.分类 3.应用条件 4.原理详解 4.1 sigmod分类函数 4.2 建立目标函数 4.3 求解相关参数 5.实列分析 5.1 读取数据&#xff08;excel文件&#xff09; 5.2 分离数据集 5.3 求解前设定 5.4 求解目标函数 5.5 预测 5.6 预测分类 5.7 准确率…

java 抽象类_java中的抽象类

普通类可以直接产生实例化对象&#xff0c;并且在普通类之中可以包含有构造方法、普通方法、static方法、常量、变量的内容。而所谓的抽象类就是指在普通类的结构里面增加抽象方法的组成部分&#xff0c;抽象方法指的是没有方法体的方法&#xff0c;同时抽象方法还必须使用abst…

Logistic回归——二分类 —— python

目录 1.简介 2.应用范围 3.分类 3.应用条件 4.原理详解 4.1 sigmod分类函数 4.2 建立目标函数 4.3 求解相关参数 5.实列分析 5.1 导入库 5.2 读取数据&#xff08;excel文件&#xff09; 5.3 分离数据集 5.4 求解前设定 5.5 求解目标函数 5.6 预测 5.7 预测分类…

dubbo官方文档_狂神说SpringBoot17:Dubbo和Zookeeper集成

狂神说SpringBoot系列连载课程&#xff0c;通俗易懂&#xff0c;基于SpringBoot2.2.5版本&#xff0c;欢迎各位狂粉转发关注学习。未经作者授权&#xff0c;禁止转载分布式理论什么是分布式系统&#xff1f;在《分布式系统原理与范型》一书中有如下定义&#xff1a;“分布式系统…