简单聊一聊callback转化成promise的实现思路

场景

我们知道异步最直接的处理方式 就是:回调函数。比如下面这个读取文件的模拟函数:

js 复制代码
const readFile = (path, callback) => {
    setTimeout(() => {
        callback(null, 'Hello World');
    }, 1000);
}

我们尝试使用这个函数去读取文件:

js 复制代码
readFile('xx/hello.txt', (err, data) => {
    if (err) {
        console.error(err);
        return;
    }
    console.log(data);
})

简单测试一下发现1s之后打印 Hello World,这没问题。

需求

但是大多数时候我们可能更希望是这种代码结构:

js 复制代码
const data = await readFile('xx/hello.txt')
console.log(data)

当然,为了错误处理,我们可能还会再包一层 try catch。

简单处理

为了实现这个需求,我们可能想到的最简单的实现是这样的:

js 复制代码
const work = async () => {
    const data = await new Promise((resolve, reject) => {
        readFile((err, data) => {
            if (err) {
                reject(err);
            }
            resolve(data)
        })
    })

    console.log(data)
}

work()

就是简单用 Promise 去包装一下。

封装

当然我们肯定不希望这样手动去使用promise包裹,如果手动写这个promise,代码写起来显得画蛇添足。我们希望的是要是有一个工具,可以自动帮我们把回调的形式自动包装成promise就好了。比如:

js 复制代码
const readFileFn = promisify(readFile)

接下来我们再这样区去使用:

js 复制代码
const work = async () => {
    const data = await readFileFn('xx/hello.txt');
    console.log(data)
}

work()

我们先简单分析一下,可以得到以下几点值得注意的地方:

  • readFileFn 调用的时机就是 readFile 调用的时机。
  • promisify 调用的结果是返回一个 promise,这个 promise 完成的时机是 callback 调用的时机。

所以我们的思路是包装一个函数传给 readFile,这个函数接收的最后一个参数就是 callback,而这个函数的返回值是一个 promise。由于函数是我们包装的,所以最后一个参数 callback 的实现由我们控制,那么我们可以在 callback 的实现中,修改先前生成的 promise 的状态。具体完整代码如下:

js 复制代码
const promisify = (fn) => (...args) => new Promise((resolve, reject) => {
    args.push((err, data) => {
        err ? reject(err) : resolve(data);
    })
    fn(...args);
})

最后再完整测试一下:

js 复制代码
const readFile = (path, callback) => {
    setTimeout(() => {
        callback(null, 'Hello World');
    }, 1000);
}

const promisify = (fn) => (...args) => new Promise((resolve, reject) => {
    args.push((err, data) => {
        err ? reject(err) : resolve(data);
    })
    fn(...args);
})

const readFileFn = promisify(readFile)
const work = async () => {
    const data = await readFileFn('xx/hello.txt');
    console.log(data)
}

work()

发现测试是OK的。同时这也是 util 这个工具包,promisify方法的源码实现逻辑。不同的是,在源码中,这个方法更多地考虑了函数属性的处理问题,不过我们这里已经实现了其核心逻辑。 值得一提的是:通过这个方法将 callback 包装成 promise 形式,需要 callback 设计放在最后一个参数位置,就像我们例子中的 readFile 方法。

相关推荐
晓得迷路了7 小时前
栗子前端技术周刊第 153 期 - Vitest 5.1、Remix v3.2、Node.js 27 Alpha...
前端·javascript·vite
Moment8 小时前
Harness Engineering 为什么是 AI Coding 的工程底座 ❓❓❓
前端·后端·面试
风骏时光牛马8 小时前
生成一个AI面试复盘标题,只需要一个标题,不需要其他的任何文本。
前端
红尘散仙17 小时前
谁说终端只能敲键盘?我用 Rust 给小说阅读器加上了鼠标
前端·后端·rust
陈随易18 小时前
C++26 草案被否,二十八年来头一回
前端·后端·程序员
ZhengEnCi18 小时前
Agent 工具怎么折叠海量输出才「爽」:Trae、Claude Code 的渐进式披露设计原理
前端·agent
IT_陈寒21 小时前
Redis内存暴涨,没想到是AOF重写捣的鬼
前端·人工智能·后端
ikoala1 天前
让 Codex 自己查账:一个省 token 的小技巧
前端·后端·ai编程
ikoala1 天前
AI 写页面像说明书,修 Bug 像贴膏药?把这两句话写进 AGENTS.md
前端·javascript·ai编程