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

相关推荐
还有你Y3 小时前
Shell 脚本语法
前端·语法·sh
踩着两条虫4 小时前
如何评价VTJ.PRO?
前端·架构·ai编程
Mh5 小时前
鼠标跟随倾斜动效
前端·css·vue.js
小码哥_常6 小时前
Kotlin类型魔法:Any、Unit、Nothing 深度探秘
前端
Web极客码7 小时前
深入了解WordPress网站访客意图
服务器·前端·wordpress
幺风8 小时前
Claude Code 源码分析 — Tool/MCP/Skill 可扩展工具系统
前端·javascript·ai编程
vjmap8 小时前
唯杰地图CAD图层加高性能特效扩展包发布
前端·gis
ZC跨境爬虫8 小时前
3D 地球卫星轨道可视化平台开发 Day7(AI异步加速+卫星系列精简+AI Agent自动评论)
前端·人工智能·3d·html·json
ID_180079054738 小时前
淘宝 API 上货 / 商品搬家 业务场景实现 + JSON 返回示例
前端·javascript·json
M ? A8 小时前
Vue 动态组件在 React 中,VuReact 会如何实现?
前端·javascript·vue.js·经验分享·react.js·面试·vureact