当前位置: 首页 > 后端技术 > Node.js

在Node.js中将回调转换为Promises

时间:2023-04-03 23:05:56 Node.js

介绍几年前,回调是在JavaScript中执行异步代码的唯一方法。回调本身几乎没有问题,最明显的是“回调地狱”。ES6中引入了Promises作为这些问题的解决方案。最后,它通过引入async/await关键字提供了更好的体验并提高了可读性。即使采用新方法,仍有许多使用回调的本机模块和库。在本文中,我们将讨论如何将JavaScript回调转换为Promises。ES6的知识会派上用场,因为我们将使用展开运算符等功能来简化操作。什么是回调回调是一个函数参数,恰好是一个函数本身。虽然我们可以创建任何函数来接受另一个函数,但回调主要用于异步操作。JavaScript是一种解释型语言,一次只能处理一行代码。有些任务可能需要很长时间才能完成,例如下载或读取大文件等。JavaScript将这些长时间运行的任务卸载到浏览器或Node.js环境中的其他进程。这样它就不会阻止其他代码的执行。通常一个异步函数会接受一个回调函数,所以它的数据可以在完成后进行处理。例如,我们将编写一个回调函数,该函数将在程序成功从硬盘读取文件后执行。所以你需要准备一个名为sample.txt的文本文件,其中包含以下内容:Helloworldfromsample.txt然后编写一个简单的Node.js脚本来读取文件:constfs=require('fs');fs.readFile('./sample.txt','utf-8',(err,data)=>{if(err){//处理错误console.error(err);return;}console.log(data);});for(leti=0;i<10;i++){console.log(i);}运行代码后会输出:0...89Helloworldfromsample.txt如果应该执行这段代码在回调看到0..9被打印到控制台之前。这是因为JavaScript的异步管理机制。读取文件后,调用输出文件内容的回调。顺便说一句,回调也可以用在同步方法中。例如Array.sort()将接受一个回调函数,允许您自定义元素的排序。接受回调的函数称为“高阶函数”。现在我们有了更好的回调方法。那么我们继续看看Promise是什么。什么是承诺?Promise是在ECMAScript2015(ES6)中引入的,以改善异步编程的体验。顾名思义,一个JavaScript对象最终会返回的“值”或“错误”应该是一个Promise。一个Promise有3种状态:Pending(挂起):初始状态用来表示异步操作还没有完成。Fulfilled:表示异步操作成功完成。Rejected:表示异步操作失败。大多数Promises最终看起来像这样:someAsynchronousFunction().then(data=>{//promiseisfulfilledconsole.log(data);}).catch(err=>{//promise被拒绝console.error(err);});Promises在现代JavaScript中非常重要,因为它们与ECMAScript2016中引入的async/await关键字一起使用。使用async/await消除了回调或then()和catch()编写异步代码的需要。如果重写前面的例子,它看起来像这样:"同步JavaScript。大多数流行的JavaScript库和新项目都将Promises与async/await关键字结合使用。但是,如果您正在更新现有库或遇到遗留代码,您可能会对将基于回调的API迁移到基于Promise的API感兴趣,这可以改善您的开发体验。让我们看一下将回调转换为Promises的几种方法。将回调转换为PromisesNode.jsPromises大多数在Node.js中接受回调的异步函数(例如fs模块)都有一个标准实现:将回调作为最后一个参数传递。例如,下面是如何在不指定文本编码的情况下使用fs.readFile()读取文件:fs.readFile('./sample.txt',(err,data)=>{if(err){console.error(错误);返回;}console.log(数据);});注意:如果您指定utf-8作为编码,则输出是一个字符串。如果未指定,则输出为Buffer。传递给这个函数的回调也应该接受错误,因为它是第一个参数。之后可以有任意数量的输出。如果您需要将函数转换为Promises以遵循这些规则,您可以使用util.promisify,这是一个包含Promises回调的原生Node.js模块。首先导入?util`模块:constutil=require('util');然后使用promisify方法将其转换为Promise:constfs=require('fs');constreadFile=util.promisify(fs.readFile);现在,使用新创建的函数作为承诺:readFile('./sample.txt','utf-8').then(data=>{console.log(data);}).catch(err=>{console.log(err);});您还可以使用以下示例中给出的async/await关键字:constfs=require('fs');constutil=require('util');constreadFile=util.promisify(fs.readFile);(async()=>{try{constcontent=awaitreadFile('./sample.txt','utf-8');console.log(content);}catch(err){console.error(err);}})();您只能在使用async创建的函数中使用await关键字,这就是使用函数包装器的原因。函数包装器也称为立即调用的函数表达式。如果您的回调不遵循此特定标准,请不要担心。util.promisify()函数可让您自定义转换的发生方式。注意:Promises被引入后不久就流行起来。Node.js已将其大部分核心功能从回调转换为基于Promise的API。如果需要使用Promises处理文件,可以使用Node.js自带的库(https://nodejs.org/docs/lates...)。现在您已经了解了如何将Node.js标准样式回调隐含到Promise。从Node.js8开始,此模块仅在Node.js上可用。如果您使用的是浏览器或早期版本的Node,创建您自己的基于Promise的函数版本是个好主意。创建您自己的承诺让我们讨论如何使用util.promisify()函数将回调转换为承诺。这个想法是创建一个包含回调函数的新Promise对象。如果回调返回错误,则拒绝带有该错误的Promise。如果回调函数返回非错误输出,则解析并输出Promise。首先将回调转换为接受具有固定参数的函数的承诺:constfs=require('fs');constreadFile=(fileName,encoding)=>{returnnewPromise((resolve,reject)=>{fs.readFile(fileName,encoding,(err,data)=>{if(err){returnreject(err);}resolve(data);});});}readFile('./sample.txt').then(data=>{console.log(data);}).catch(err=>{console.log(呃);});新函数readFile()接受这两个参数。然后创建一个包装函数并接受回调的新Promise对象,在本例中为fs.readFile()。拒绝Promise而不是返回错误。所以代码并没有立即输出数据,而是先解析Promise。然后像以前一样使用基于Promise的readFile()函数。接下来看看接受动态数量参数的函数:constgetMaxCustom=(callback,...args)=>{letmax=-Infinity;for(letiofargs){if(i>max){max=i;}}callback(max);}getMaxCustom((max)=>{console.log('Maxis'+max)},10,2,23,1,111,20);第一个参数是回调参数,这使得它在接受回调的函数之间有点不同。转换为promise的方式和前面的例子一样。创建一个新的Promise对象,它包装使用回调的函数。如果遇到错误,它会拒绝,并在出现结果时解决。我们的promise版本看起来像这样:constgetMaxPromise=(...args)=>{returnnewPromise((resolve)=>{getMaxCustom((max)=>{resolve(max);},...args);});}getMaxCustom(10,2,23,1,111,20).then(max=>console.log(max));创建承诺时,函数是否以非标准方式使用回调或使用许多参数都无关紧要。我们完全可以控制它是如何完成的,原理是一样的。总结尽管回调现在是在JavaScript中利用异步代码的默认方式,但Promises是一种更现代且更易于使用的方法。如果您遇到使用回调的代码库,现在是将其转换为Promise的时候了。在本文中,我们首先学习了如何使用Node.js中的utils.promisfy()方法将接受回调的函数转换为Promise。然后,您了解了如何创建自己的Promise对象,在不使用外部库的情况下将函数包装在接受回调的对象中。通过这种方式,许多遗留JavaScript代码可以轻松地与现代代码库混合。