1075 字
5 分钟
JavaScript promisify

题目描述
实现一个 promisify 函数,将 Node.js 风格的错误优先回调函数转换为 Promise 风格函数:
- 转换遵循 (err, value) 回调约定的函数
- 支持保留原始函数的 this 上下文
- 处理多参数回调(返回结果对象)
- 支持自定义 Promise 实现(如 Bluebird)
- 处理回调函数被多次调用的情况
要求完整实现 promisify 逻辑,并通过测试用例验证。
解题思路
- 函数包装:返回接受相同参数的新函数(返回Promise)
- 上下文绑定:使用 apply 保持原始函数上下文
- 回调处理:创建自定义回调处理错误和结果
- 多参数处理:收集所有回调参数作为结果
- 单次解析:确保 Promise 只 resolve/reject 一次
- 边界处理:处理同步错误和多次回调
关键洞察
- 回调约定转换:将错误优先回调转为 Promise 的 resolve/reject
- 上下文保持:通过闭包保存原始函数的 this 绑定
- 多参数支持:回调参数除错误外转为结果对象
- 单次执行保证:防止多次调用导致 Promise 状态变更
- 兼容性处理:支持自定义 Promise 实现
- 错误优先原则: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);
业务场景
- Node.js 模块改造:将 fs、child_process 等模块转为 Promise
- 数据库操作:转换 MongoDB/MySQL 驱动回调为 Promise
- API 封装:统一处理第三方库回调风格接口
- Async/Await 集成:在 async 函数中使用传统回调库
- 错误处理简化:用 catch 统一处理错误替代嵌套判断
- 遗留代码改造:迁移旧代码库到 Promise/Async 语法
转型优势:
- 消除回调地狱,提升代码可读性
- 统一错误处理机制
- 与现代 async/await 语法无缝集成
- 简化复杂异步流程控制
- 提高代码可维护性和可测试性
相似题目
- 实现 Promise A+ 规范(困难) 核心:状态机 + 异步回调队列
- 实现 Async/Await(困难) 核心:Generator + Promise 自动执行器
- 实现回调函数限流(中等) 核心:队列控制 + 最大并发数
- 实现 Promise.all(中等) 核心:多 Promise 并行执行与结果收集
- 实现取消令牌(中等) 核心:AbortController + Promise 拒绝