怎样将 JavaScript 回调转换为 Promise?办法介绍
在几年前,回调是 JavaScript 中实现施行异步代码的独一办法。回调本身几乎没有什么问题,最值得留意的是“回调地狱”。
在 ES6 中引入了 Promise 作为这些问题的解决方案。最后通过引入 async/await
关键字来供给更好的体验并提高了可读性。
即便有了新的办法,但是依然有很多使用回调的原生模块和库。在本文中,我们将计议怎样将 JavaScript 回调转换为 Promise。 ES6 的知识将会派上用处,由于我们将会使用 展开操纵符之类的功效来简化要做的事情。
什么是回调
回调是一个函数参数,刚好是一个函数本身。虽然我们可以创立任何函数来接受另一个函数,但回调主要用于异步操纵。
JavaScript 是一种说明性说话,一次只能处置一行代码。有些任务大概需要很长时间才能完成,例如下载或读取大文件等。 JavaScript 将这些运转时间很长的任务转移到阅读器或 Node.js 环境中的其他进程中。这样它就不会阻挠其他代码的施行。
平常异步函数会接受回调函数,所以完成之后可以处置其数据。
举个例子,我们将编写一个回调函数,这个函数会在程序成功从硬盘读取文件之后施行。
所以需要预备一个名为 sample.txt
的文本文件,其中包括以下内容:
Hello world from sample.txt
然后写一个简便的 Node.js 足原本读取文件:
const fs = require('fs'); fs.readFile('./sample.txt', 'utf-8', (err, data) => { if (err) { // 处置错误 console.error(err); return; } console.log(data); }); for (let i = 0; i < 10; i++) { console.log(i); }
运转代码后将会输出:
0 ... 8 9 Hello world from sample.txt
假如这段代码,应当在施行回调此前看到 0..9
被输出到操纵台。这是由于 JavaScript 的异步治理机制。在读取文件完毕之后,输出文件内容的回调才被调取。
顺便说明一下,回调也可以在同步办法中使用。例如 Array.sort()
会接受一个回调函数,这个函数同意你自定义元素的排序方式。
接受回调的函数被称为“高阶函数”。
此刻我们有了一个更好的回调办法。那么们连续看看什么是 Promise。
什么是 Promise
在 ECMAScript 2015(ES6)中引入了 Promise,用来改善在异步编程方面的体验。望文生义,JavaScript 对象终究将返回的“值”或“错误”应当是一个 Promise。
一个 Promise 有 3 个状态:
- Pending(待处置): 用来指示异步操纵尚未完成的初始状态。
- Fulfilled(已完成):表示异步操纵已成功完成。
- Rejected(回绝):表示异步操纵失败。
大多数 Promise 终究看起来像这样:
someAsynchronousFunction() .then(data => { // promise 被完成 console.log(data); }) .catch(err => { // promise 被回绝 console.error(err); });
Promise 在现代 JavaScript 中非常重要,由于它们与 ECMAScript 2016 中引入的 async/await
关键字一起使用。使用 async / await
就不需要再用回调或 then()
和 catch()
来编写异步代码。
假如要改写前面的例子,应当是这样:
try { const data = await someAsynchronousFunction(); } catch(err) { // promise 被回绝 console.error(err); }
这看起来很像“一样的”同步 JavaScript。大多数流行的JavaScript库和新项目都把 Promises 与 async/await
关键字放在一起用。
但是,假如你要更新现有的库或碰到旧的代码,则大概会对将基于回调的 API 迁移到基于 Promise 的 API 感乐趣,这样可以改善你的开发体验。
来看一下将回调转换为 Promise 的几种办法。
将回调转换为 Promise
Node.js Promise
大多数在 Node.js 中接受回调的异步函数(例如 fs
模块)有标准的实现方式:把回调作为最后一个参数传递。
例如这是在不指定文本编码的状况下用 fs.readFile()
读取文件的办法:
fs.readFile('./sample.txt', (err, data) => { if (err) { console.error(err); return; } console.log(data); });
留意:假如你指定 utf-8
作为编码,那么得到的输出是一个字符串。假如不指定得到的输出是 Buffer
。
别的传给这个函数的回调应接受 Error
,由于它是第一个参数。之后可以有任意数目的输出。
假如你需要转换为 Promise 的函数遵照这些规则,那么可以用 util.promisify ,这是一个原生 Node.js 模块,其中包括对 Promise 的回调。
第一导入?util`模块:
const util = require('util');
然后用 promisify
办法将其转换为 Promise:
const fs = require('fs'); const readFile = util.promisify(fs.readFile);
此刻,把新创立的函数用作 promise:
readFile('./sample.txt', 'utf-8') .then(data => { console.log(data); }) .catch(err => { console.log(err); });
别的也可以用下面这个示例中给出的 async/await
关键字:
const fs = require('fs'); const util = require('util'); const readFile = util.promisify(fs.readFile); (async () => { try { const content = await readFile('./sample.txt', 'utf-8'); console.log(content); } catch (err) { console.error(err); } })();
你只能在用 async
创立的函数中使用 await
关键字,这也是为什么要使用函数包装器的缘由。函数包装器也被称为马上调取的函数表达式。
假如你的回调不遵照这个特定标准也不消担忧。 util.promisify()
函数可让你自定义转换是怎样发生的。
留意: Promise 在被引入后不久就开端流行了。 Node.js 已经将大部分中心函数从回调转换成了基于 Promise 的API。
假如需要用 Promise 处置文件,可以用 Node.js 附带的库(https://nodejs.org/docs/lates...)。
此刻你已经理解了怎样将 Node.js 标准样式回调隐含到 Promise 中。从 Node.js 8 开端,这个模块仅在 Node.js 上可用。假如你用的是阅读器或早期版本版本的 Node,则最好创立本人的基于 Promise 的函数版本。
创立你本人的 Promise
让我们计议一下怎样把回调转为 util.promisify()
函数的 promise。
思绪是创立一个新的包括回调函数的 Promise 对象。假如回调函数返回错误,就回绝带有该错误的Promise。假如回调函数返回非错误输出,就解决并输出 Promise。
先把回调转换为一个接受牢固参数的函数的 promise 开端:
const fs = require('fs'); const readFile = (fileName, encoding) => { return new Promise((resolve, reject) => { fs.readFile(fileName, encoding, (err, data) => { if (err) { return reject(err); } resolve(data); }); }); } readFile('./sample.txt') .then(data => { console.log(data); }) .catch(err => { console.log(err); });
新函数 readFile()
接受了用来读取 fs.readFile()
文件的两个参数。然后创立一个新的 Promise 对象,该对象包装了该函数,并接受回调,在本例中为 fs.readFile()
。
要 reject
Promise 而不是返回错误。所以代码中没有马上把数据输出,而是先 resolve
了Promise。然后像之前一样使用基于 Promise 的 readFile()
函数。
接下来看看接受动态数目参数的函数:
const getMaxCustom = (callback, ...args) => { let max = -Infinity; for (let i of args) { if (i > max) { max = i; } } callback(max); } getMaxCustom((max) => { console.log('Max is ' + max) }, 10, 2, 23, 1, 111, 20);
第一个参数是 callback 参数,这使它在接受回调的函数中有点不同凡响。
转换为 promise 的方式和上一个例子一样。创立一个新的 Promise 对象,这个对象包装使用回调的函数。假如碰到错误,就 reject
,当结果显现时将会 resolve
。
我们的 promise 版本如下:
const getMaxPromise = (...args) => { return new Promise((resolve) => { getMaxCustom((max) => { resolve(max); }, ...args); }); } getMaxCustom(10, 2, 23, 1, 111, 20) .then(max => console.log(max));
在创立 promise 时,不管函数是以非标准方式还是带有很多参数使用回调都可有可无。我们可以完全操纵它的完成方式,并且道理是一样的。
总结
尽管此刻回调已成为 JavaScript 中利用异步代码的默许办法,但 Promise 是一种更现代的办法,它更容易使用。假如碰到了使用回调的代码库,那么此刻就可以把它转换为 Promise。
在本文中,我们第一学到了怎样 在Node.js 中使用 utils.promisfy()
办法将接受回调的函数转换为 Promise。然后,理解了怎样创立本人的 Promise 对象,并在对象中包装了无需使用外部库即可接受回调的函数。这样很多旧 JavaScript 代码可以轻松地与现代的代码库和混合在一起。