java 异步得到函数返回值_使用JavaScript进行异步编程-程序员宅基地

技术标签: java 异步得到函数返回值  

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

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_39559277/article/details/110248317

智能推荐

pytorch学习(一)利用pytorch训练一个最简单的分类器-------(基于CIFAR10数据集)的句句讲解_classes[labels[i]] for i in range(4)]-程序员宅基地

文章浏览阅读1.3w次,点赞75次,收藏273次。刚学pytorch两周,利用这个分类器学习pytorch的如何运用训练一个分类网络分为以下几个步骤:1数据的加载及预处理2网络模型的设置3.定义损失函数及优化器4.用训练集训练网络5.用测试集测试网络1.数据的加载及预处理1.加载数据集(训练集和测试集)2.扩充数据集防止过拟合2.网络模型的设置网络模型的设置主要是网络结构的设置:本次我们使用的是简单的一个类网络的简单设置..._classes[labels[i]] for i in range(4)]

java判断是否空值_JAVA 判断对象内容是否含有空值-程序员宅基地

文章浏览阅读1.5k次。简单判断对象是否含有NULL值,以及信息描述。packagecom.sicdt.sicsign.bill.api.util;importjava.lang.reflect.InvocationTargetException;importjava.lang.reflect.Method;importjava.util.ArrayList;importjava.util.Arrays;importja..._java如何判断一个twodiseasescreening对象是否有值

实例:GridView实现CheckBox的多选或单选,并根据所选择的行进行数据操作_asp.net gridview checkbox 多选-程序员宅基地

文章浏览阅读1.2w次。实例说明: 有一个用户列表,其中每一个用户对应一个状态。状态有两个值,分别是已激活,未激活。 现要求实现这样的功能, 1. 列表显示出用户名及对应的激活状态。 2. 列表中的每一行行首有一个CheckBox,列表底部有一个Button(激活)。使用者先勾选CheckBox(可多选),在点击激活按钮,即可激活相应勾选的用户。 3. 对于当前状态是激活状态的用户,其行首不应该显示CheckBox.功能截图: 涉及到的知识: Gr_asp.net gridview checkbox 多选

ssm整合spring-security遇到的404错误、一直重定向于登入界面的错误_springsecurity 404-程序员宅基地

文章浏览阅读4.9k次,点赞3次,收藏6次。文章目录1. 404错误1.1 第一种可能1.2 第二种可能2. 无论登入成功还是失败一直重定向在登入界面2.1 第一个可能2.2 第二个可能3. 最后附上我的spring-security的配置文件1. 404错误1.1 第一种可能如果你设置的登入页面是.html页面,则会出现404的问题。因为spring-security要操作页面,都是请求springmvc得到的。spring-sec..._springsecurity 404

android 手机通过usb数据线与OTG设备通信_android otg 发指令-程序员宅基地

文章浏览阅读1.8k次。1.首先在AndroidManifest.xml文件中添加所需要的权限<uses-feature android:name="android.hardware.usb.host" /><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /><uses-permis..._android otg 发指令

MySql取得日期(前一天、某一天) 当前时间等_mysql 查询日期等于今天-程序员宅基地

文章浏览阅读9.2k次。https://www.cnblogs.com/aprils/p/4519796.html取得当天:SELECT curdate();mysql> SELECT curdate();+------------+| curdate()|+------------+| 2013-07-29 |+------------+取得当前日期:mysql> s..._mysql 查询日期等于今天

随便推点

@Component的作用_@component 作用-程序员宅基地

文章浏览阅读1.1k次。@TOC版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/m0_37626813/article/details/78558010 今天在写程序的时候看见一个以前没有见过的注解(@Component),在网上查找过后,经过实践,决定把它记录下来。 1、@controller 控制器(注入服务)用于标注控制层,相当于st..._@component 作用

javascript 自调用匿名函数-程序员宅基地

文章浏览阅读695次。通常定义函数并调用 function a(){ } a(); 或是 var a = function(){ } a(); 但是查看jquery源码会发现它最外层的架构是这样的 (function( window, undefined ) { // jquery code })(window); 解释:首先jquery是定义了一个匿名函数(

如何实现文件的上传功能?_专机上传怎么实现-程序员宅基地

文章浏览阅读7.1k次,点赞2次,收藏16次。如何用servlet如何实现文件上传:一、用servlet如何实现文件上传: 1.需要先获取你把上传的文件放到哪里(也就是你的存储路径)2.如果你需要上传的不只是一个文件的话,需要先定一个Part集合来得到你要上传的集合,通过用户的请求3.先通过request去拿到你要上传的文件用Part对象接受4.然后就是通过part获取请求头part.getHeader(“content-dis..._专机上传怎么实现

iOS UITableView中异步加载图片 - 解决方案_tableview reloadrows 异步-程序员宅基地

文章浏览阅读6.4k次。问题背景:需要在UITableView中的每一行下载图片,之前使用placeholder,下载好后存在cache中。解决方案:方案一:使用SDWebImage:https://github.com/rs/SDWebImage如何安装及使用在git页面有详细解释,具体使用的代码:#import ...- (UITableViewCell *)tableView_tableview reloadrows 异步

Linux——Squid代理服务器_squid 代理linux-程序员宅基地

文章浏览阅读244次。二、安装及运行控制1.编译安装Aquid—prefix=/usr/local/squid:安装目录—sysconfdir=/etc:单独将配置文件修改到其他目录。—enable-linux-netfilter:使用内核过滤。—enable-async-io=值:异步I/O,提升存储性能—enable-default-err-Ianguage=Simplify_Chinese:错误信..._squid 代理linux

2021-04-16 mp4==>wav+txt==>音频段00:00:00-99:99:99_mp4电台频段-程序员宅基地

文章浏览阅读86次。标注自用 适用性不行 顶多作为tools 拆开用~1)json转txt 并提取需要的类temp数据import jsonimport osimport numpy as npdef readjson(): # // 打开json文件,文件路径要自己改 with open("./draft.json", 'r', encoding='utf-8') as f: temp = json.loads(f.read()) # // 获得 中..._mp4电台频段

推荐文章

热门文章

相关标签