我们知道在浏览器中 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
函数导出,供其他模块使用。