1075 字
5 分钟
JavaScript promisify

题目描述#

实现一个 promisify 函数,将 Node.js 风格的错误优先回调函数转换为 Promise 风格函数:

  1. 转换遵循 (err, value) 回调约定的函数
  2. 支持保留原始函数的 this 上下文
  3. 处理多参数回调(返回结果对象)
  4. 支持自定义 Promise 实现(如 Bluebird)
  5. 处理回调函数被多次调用的情况

要求完整实现 promisify 逻辑,并通过测试用例验证。

解题思路#

  1. 函数包装:返回接受相同参数的新函数(返回Promise)
  2. 上下文绑定:使用 apply 保持原始函数上下文
  3. 回调处理:创建自定义回调处理错误和结果
  4. 多参数处理:收集所有回调参数作为结果
  5. 单次解析:确保 Promise 只 resolve/reject 一次
  6. 边界处理:处理同步错误和多次回调

关键洞察#

  1. 回调约定转换:将错误优先回调转为 Promise 的 resolve/reject
  2. 上下文保持:通过闭包保存原始函数的 this 绑定
  3. 多参数支持:回调参数除错误外转为结果对象
  4. 单次执行保证:防止多次调用导致 Promise 状态变更
  5. 兼容性处理:支持自定义 Promise 实现
  6. 错误优先原则:err 存在时立即 reject,否则 resolve

代码流程#

flowchart TD 
	A[调用promisify函数] --> B[返回新函数] 
	B --> C[创建Promise] 
	C --> D[执行原函数] 
	D -->|成功| E[收集结果] 
	D -->|失败| F[拒绝Promise] 
	E --> G[解析Promise] 
	F --> G

代码实现#

function promisify(original, customPromise) {
// 使用自定义Promise实现或原生Promise
const PromiseImpl = customPromise || Promise;
return function(...args) {
return new PromiseImpl((resolve, reject) => {
// 创建回调函数
const callback = (err, ...values) => {
if (err) return reject(err);
// 多参数处理:单值直接返回,多值返回对象
resolve(values.length <= 1 ? values : values);
};
// 添加回调到参数列表
try {
// 保留this上下文
original.apply(this, [...args, callback]);
} catch (error) {
// 处理同步错误
reject(error);
}
});
};
}
// 高级版本:支持多参数命名
promisify.custom = Symbol('promisify.custom');
function promisifyAdvanced(original, customPromise) {
const PromiseImpl = customPromise || Promise;
// 处理自定义promisify函数
if (original[promisify.custom]) {
return function(...args) {
return PromiseImpl.resolve().then(() =>
original[promisify.custom](...args)
);
};
}
return function(...args) {
return new PromiseImpl((resolve, reject) => {
// 防止多次调用
let called = false;
const callback = (err, ...values) => {
if (called) return;
called = true;
if (err) return reject(err);
// 处理多参数回调
if (values.length === 0) return resolve(undefined);
if (values.length === 1) return resolve(values);
// 创建结果对象
const result = {};
const callbackParams = original.toString().match(/function\s.*?$$(.*?)$$/).split(',');
const callbackArgNames = callbackParams.slice(-1).replace(/\s+/g, '').split(')').split('=').split(' ');
if (callbackArgNames.length > 1) {
// 使用命名参数
const [, ...paramNames] = callbackArgNames;
paramNames.forEach((name, index) => {
result[name] = values[index];
});
} else {
// 使用索引参数
values.forEach((value, index) => {
result[`arg${index}`] = value;
});
}
resolve(result);
};
try {
// 调用原始函数
original.apply(this, [...args, callback]);
} catch (error) {
if (!called) {
called = true;
reject(error);
}
}
});
};
}

测试代码#

// Node.js 风格回调函数
function readFile(path, encoding, callback) {
// 模拟异步操作
setTimeout(() => {
if (path === 'error') {
callback(new Error('File not found'));
} else {
callback(null, `Content of ${path}`, 'Additional data');
}
}, 100);
}
// 转换为Promise版本
const readFilePromise = promisify(readFile);
// 基本使用
readFilePromise('test.txt', 'utf8')
.then(data => console.log(data))
.catch(err => console.error(err));
// 多参数处理
const advancedReadFile = promisifyAdvanced(readFile);
advancedReadFile('config.json', 'utf8')
.then(result => {
console.log(result.arg0); // "Content of config.json"
console.log(result.arg1); // "Additional data"
});
// 错误处理
readFilePromise('error', 'utf8')
.catch(err => console.error('Error:', err.message)); // "File not found"
// 自定义Promise实现(Bluebird等)
const customPromise = require('bluebird');
const customPromisified = promisify(readFile, customPromise);

业务场景#

  1. Node.js 模块改造:将 fs、child_process 等模块转为 Promise
  2. 数据库操作:转换 MongoDB/MySQL 驱动回调为 Promise
  3. API 封装:统一处理第三方库回调风格接口
  4. Async/Await 集成:在 async 函数中使用传统回调库
  5. 错误处理简化:用 catch 统一处理错误替代嵌套判断
  6. 遗留代码改造:迁移旧代码库到 Promise/Async 语法

转型优势

  • 消除回调地狱,提升代码可读性
  • 统一错误处理机制
  • 与现代 async/await 语法无缝集成
  • 简化复杂异步流程控制
  • 提高代码可维护性和可测试性

相似题目#

  1. 实现 Promise A+ 规范(困难) 核心:状态机 + 异步回调队列
  2. 实现 Async/Await(困难) 核心:Generator + Promise 自动执行器
  3. 实现回调函数限流(中等) 核心:队列控制 + 最大并发数
  4. 实现 Promise.all(中等) 核心:多 Promise 并行执行与结果收集
  5. 实现取消令牌(中等) 核心:AbortController + Promise 拒绝
JavaScript promisify
https://website-truelovings-projects.vercel.app/posts/code/javascript/javascript-promisify/
作者
欢迎来到StarSky的网站!
发布于
2024-06-17
许可协议
CC BY-NC-SA 4.0