实现Promisify
将传统的回调函数风格的异步函数转换为返回Promise的函数
问题
在Node.js和传统的JavaScript异步编程中,很多API使用回调函数(callback)的方式处理异步操作,通常遵循”错误优先”的回调约定(error-first callback),即回调函数的第一个参数是错误对象,后续参数是结果数据。
Promisify的作用是将这种回调风格的函数转换为返回Promise的函数,使代码更加现代化,支持async/await语法,提高代码可读性。
解答
/**
* 将回调风格的函数转换为返回Promise的函数
* @param {Function} fn - 需要转换的函数(遵循error-first callback约定)
* @returns {Function} 返回Promise的函数
*/
function promisify(fn) {
return function (...args) {
return new Promise((resolve, reject) => {
// 在原参数后添加回调函数
fn.call(this, ...args, (err, ...results) => {
if (err) {
// 如果有错误,reject Promise
reject(err);
} else {
// 如果成功,resolve结果
// 如果只有一个结果,直接返回;多个结果返回数组
resolve(results.length <= 1 ? results[0] : results);
}
});
});
};
}
/**
* 进阶版:支持自定义回调参数位置
* @param {Function} fn - 需要转换的函数
* @param {Object} options - 配置选项
* @returns {Function} 返回Promise的函数
*/
function promisifyAdvanced(fn, options = {}) {
const { multiArgs = false } = options;
return function (...args) {
return new Promise((resolve, reject) => {
const callback = (err, ...results) => {
if (err) {
reject(err);
} else {
// multiArgs为true时,始终返回数组
resolve(multiArgs ? results : results[0]);
}
};
// 保持this上下文
fn.call(this, ...args, callback);
});
};
}
使用示例
// 示例1:基础使用 - 转换fs.readFile
const fs = require('fs');
// 原始回调风格
fs.readFile('test.txt', 'utf8', (err, data) => {
if (err) {
console.error(err);
return;
}
console.log(data);
});
// 使用promisify转换
const readFilePromise = promisify(fs.readFile);
// 使用Promise方式
readFilePromise('test.txt', 'utf8')
.then(data => console.log(data))
.catch(err => console.error(err));
// 使用async/await方式
async function readFile() {
try {
const data = await readFilePromise('test.txt', 'utf8');
console.log(data);
} catch (err) {
console.error(err);
}
}
// 示例2:自定义回调函数
function getUserInfo(userId, callback) {
setTimeout(() => {
if (userId <= 0) {
callback(new Error('Invalid user id'));
} else {
callback(null, { id: userId, name: 'John', age: 25 });
}
}, 1000);
}
const getUserInfoPromise = promisify(getUserInfo);
getUserInfoPromise(1)
.then(user => console.log(user)) // { id: 1, name: 'John', age: 25 }
.catch(err => console.error(err));
// 示例3:处理多个返回值
function getMultipleValues(callback) {
setTimeout(() => {
callback(null, 'value1', 'value2', 'value3');
}, 1000);
}
const getMultipleValuesPromise = promisifyAdvanced(getMultipleValues, {
multiArgs: true
});
getMultipleValuesPromise()
.then(results => console.log(results)); // ['value1', 'value2', 'value3']
// 示例4:保持this上下文
const obj = {
name: 'MyObject',
getData(callback) {
setTimeout(() => {
callback(null, `Data from ${this.name}`);
}, 1000);
}
};
obj.getDataPromise = promisify(obj.getData);
obj.getDataPromise()
.then(data => console.log(data)); // 'Data from MyObject'
关键点
-
Error-First Callback约定:回调函数的第一个参数必须是错误对象,这是Node.js的标准约定
-
参数传递:使用剩余参数(…args)收集原函数的所有参数,并在调用时展开传递
-
回调函数注入:在原参数列表末尾添加自定义的回调函数,用于捕获异步结果
-
Promise封装:根据回调的第一个参数(err)判断成功或失败,分别调用resolve或reject
-
this上下文保持:使用fn.call(this, …)确保转换后的函数保持原有的this上下文
-
多返回值处理:当回调函数有多个结果参数时,可以选择返回单个值或数组
-
兼容性考虑:Node.js内置了util.promisify方法,实际项目中可以直接使用,但理解其实现原理对深入掌握Promise很有帮助
目录