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

相关推荐
zhangxingchao11 分钟前
Android开发者如何快速上手Flutter开发
前端
空&白26 分钟前
css元素的after制作斜向的删除线
前端·css
海盐泡泡龟26 分钟前
“组件、路由懒加载”,在 Vue3 和 React 中分别如何实现? (copy)
前端·javascript·react.js
_揽1 小时前
html如何在一张图片上的某一个区域做到点击事件
前端·html
踢足球的,程序猿1 小时前
从 Vue 2.0 进阶到 Vue 3.0 的核心技术解析指南
前端·javascript·vue.js·前端框架·html
冷凌爱1 小时前
Fetch与Axios:区别、联系、优缺点及使用差异
前端·node.js·js
袁煦丞1 小时前
跨平台终端王者Tabby:cpolar内网穿透实验室第632个成功挑战
前端·程序员·远程工作
Sailing1 小时前
Grafana-mcp-analyzer:基于 MCP 的轻量 AI 分析监控图表的运维神器!
前端·node.js·mcp
阿山同学.2 小时前
AWS 亚马逊 S3存储桶直传 前端demo 复制即可使用
前端·javascript·aws
Jolyne_2 小时前
grid 实现完美的水平铺满、间隔一致的自适应布局
前端·css