
我们知道在浏览器中 JavaScript 是单线程运行的,而回调函数曾经是 JavaScript 中实现异步函数的主要方式,面对这样的嵌套回调,处理错误也会变得非常困难:你必须在“金字塔”的每一级处理错误,而不是在最高一级一次完成错误处理,所以大多数现代异步 API 采用的都是 Promise 的形式。 下面就请你以 Node.js 中常用的读取文件操作为例,封装一个 Promisefy 函数,将回调形式调用的读取文件方法转换成一个 Promise 的版本。
目录结构如下:
├── index.js
└── test.md 其中:
index.js 是需要补充代码的 js 文件。test.md 供读取的示例文件。注意:打开环境后发现缺少项目代码,请复制下述命令至命令行进行下载。
cd /home/project
wget https://labfile.oss.aliyuncs.com/courses/18164/dist_01.zip
unzip dist_01.zip
mv dist/* ./
rm -rf dist*请在 index.js 文件中的补全代码,完成 promisefy 函数的封装。将 fs 中的 readFile 方法 promise 化。也就是说 readFileSync 方法执行后,会返回一个 promise,可以调用 then 方法执行成功的回调或失败的回调。
在实际应用中,一个函数满足这几个条件,就可以被 promisify 化:
在控制台运行:
node index此时应打印出 true,即:回调形式的 fs.readFile 方法读取同个文件的结果与 Promise 形式读取结果一致。
const fs = require('fs');
const path = require('path');
const textPath = path.join(__dirname, '/test.md');
// 读取示例文件
fs.readFile(textPath, 'utf8', (err, contrast) => {
// 通过 promisify 转化为链式调用
const readFileSync = promisify(fs.readFile);
readFileSync(textPath, 'utf8')
.then((res) => {
console.log(res === contrast); // 此处结果预期:true,即 promise 返回内容与前面读取内容一致
})
.catch((err) => {
console.error(err);
});
});
const promisify = (fn) => {
return function (...args) {
return new Promise((resolve, reject) => {
// 将回调函数作为最后一个参数传入原函数
fn(...args, (err, result) => {
if (err) {
// 如果发生错误,拒绝 Promise
reject(err);
} else {
// 如果没有错误,解决 Promise 并返回结果
resolve(result);
}
});
});
};
};
module.exports = promisify; // 请勿删除该行代码这段 Node.js 代码的主要目的是演示如何将一个遵循错误优先回调风格的函数(如 fs.readFile)转换为返回 Promise 的函数,以便使用 Promise 的链式调用特性。具体步骤是先使用传统的回调方式读取文件内容,然后将 fs.readFile 封装成返回 Promise 的函数再次读取同一文件,最后比较两次读取的内容是否一致。
1. 引入模块
const fs = require('fs');
const path = require('path');fs 模块是 Node.js 内置的文件系统模块,用于对文件进行读写等操作。path 模块是 Node.js 内置的路径处理模块,用于处理和转换文件路径。2. 构建文件路径
const textPath = path.join(__dirname, '/test.md');__dirname 是 Node.js 中的一个全局变量,表示当前执行脚本所在的目录。path.join() 方法用于将多个路径片段拼接成一个完整的路径,这里将当前目录和 test.md 文件拼接在一起,得到文件的完整路径。3. 使用传统回调方式读取文件
fs.readFile(textPath, 'utf8', (err, contrast) => {
// 通过 promisify 转化为链式调用
const readFileSync = promisify(fs.readFile);
readFileSync(textPath, 'utf8')
.then((res) => {
console.log(res === contrast); // 此处结果预期:true,即 promise 返回内容与前面读取内容一致
})
.catch((err) => {
console.error(err);
});
});fs.readFile(textPath, 'utf8', callback):使用 fs 模块的 readFile 方法异步读取文件内容。 textPath 是要读取的文件的路径。'utf8' 表示以 UTF-8 编码格式读取文件,这样读取出来的内容是字符串类型。callback 是一个回调函数,当文件读取完成后会调用该函数,它接受两个参数: err:如果读取过程中发生错误,err 会包含错误信息;如果没有错误,err 为 null。contrast:读取到的文件内容。promisify(fs.readFile) 将 fs.readFile 转换为返回 Promise 的函数 readFileSync。readFileSync(textPath, 'utf8') 再次读取文件,返回一个 Promise 对象。 then 方法处理 Promise 成功的情况,将读取到的内容 res 与之前通过回调方式读取的内容 contrast 进行比较,并将结果打印到控制台。catch 方法处理 Promise 失败的情况,将错误信息打印到控制台。4. promisify 函数的实现
const promisify = (fn) => {
return function (...args) {
return new Promise((resolve, reject) => {
// 将回调函数作为最后一个参数传入原函数
fn(...args, (err, result) => {
if (err) {
// 如果发生错误,拒绝 Promise
reject(err);
} else {
// 如果没有错误,解决 Promise 并返回结果
resolve(result);
}
});
});
};
};promisify 函数接受一个遵循错误优先回调风格的函数 fn 作为参数。...args(使用扩展运算符收集参数)。fn,并将收集到的参数 ...args 传入,同时在最后添加一个回调函数。 err 不为 null,表示发生了错误,调用 reject(err) 拒绝 Promise。err 为 null,表示没有错误,调用 resolve(result) 解决 Promise 并返回结果。5. 导出 promisify 函数
module.exports = promisify; // 请勿删除该行代码将 promisify 函数导出,以便在其他模块中可以使用。
工作流程▶️
fs 和 path 模块,为后续的文件操作和路径处理做准备。path.join 方法构建要读取的文件的完整路径。fs.readFile 异步读取文件内容,当读取完成后,进入回调函数。promisify 函数将 fs.readFile 封装成返回 Promise 的函数 readFileSync。readFileSync 再次读取文件,返回一个 Promise 对象。then 方法处理 Promise 成功的情况,比较两次读取的内容是否一致;使用 catch 方法处理 Promise 失败的情况,打印错误信息。promisify 函数导出,供其他模块使用。