介绍
在几年前,回调是 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 代码能够轻松地与古代的代码库和混合在一起。
本文首发微信公众号:前端先锋
欢送扫描二维码关注公众号,每天都给你推送陈腐的前端技术文章
欢送持续浏览本专栏其它高赞文章:
- 深刻了解 Shadow DOM v1
- 一步步教你用 WebVR 实现虚拟现实游戏
- 13 个帮你进步开发效率的古代 CSS 框架
- 疾速上手 BootstrapVue
- JavaScript 引擎是如何工作的?从调用栈到 Promise 你须要晓得的所有
- WebSocket 实战:在 Node 和 React 之间进行实时通信
- 对于 Git 的 20 个面试题
- 深刻解析 Node.js 的 console.log
- Node.js 到底是什么?
- 30 分钟用 Node.js 构建一个 API 服务器
- Javascript 的对象拷贝
- 程序员 30 岁前月薪达不到 30K,该何去何从
- 14 个最好的 JavaScript 数据可视化库
- 8 个给前端的顶级 VS Code 扩大插件
- Node.js 多线程齐全指南
- 把 HTML 转成 PDF 的 4 个计划及实现
- 更多文章 …