简单聊一聊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 方法。

相关推荐
breeze jiang3 分钟前
React + WebGPU 在浏览器运行 DeepSeek:从 Worker 通信到流式生成
前端·javascript·react.js
a11177614 分钟前
家居生成器 Cartoon 开源项目 3D web
前端·开源
小林ixn21 分钟前
前端卡顿终结者:用 useRef 把 Web Worker 请进 React 项目
前端·react.js·前端框架
sunly_21 分钟前
React 三个重要概念:Ref、Props、State 详解
前端·javascript·react.js
weixin_4316004422 分钟前
前端对接 SSE 的两种常见方式
前端·后端·学习·ai·sse·nest.js
Mh28 分钟前
如何使用GSAP实现一个 `pinned` 滚动楼层叙事?
前端·javascript·css
用户938515635079 小时前
从零在浏览器里跑 DeepSeek-R1:WebGPU + Transformer.js 全链路实战
前端·设计模式·typescript
CodeSheep10 小时前
稚晖君公司人事大变动,来了!
前端·后端·程序员
鱼樱前端10 小时前
用 AI 做内容变收入
前端·人工智能·ai编程
浮生望10 小时前
React + TypeScript 组件设计进阶:从类型约束到无状态组件的三次进化
前端