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

相关推荐
程序员爱技术5 分钟前
Vue 2 + JavaScript + vue-count-to 集成案例
前端·javascript·vue.js
并不会1 小时前
常见 CSS 选择器用法
前端·css·学习·html·前端开发·css选择器
衣乌安、1 小时前
【CSS】居中样式
前端·css·css3
兔老大的胡萝卜1 小时前
ppk谈JavaScript,悟透JavaScript,精通CSS高级Web,JavaScript DOM编程艺术,高性能JavaScript pdf
前端·javascript
低代码布道师1 小时前
CSS的三个重点
前端·css
耶啵奶膘3 小时前
uniapp-是否删除
linux·前端·uni-app
王哈哈^_^4 小时前
【数据集】【YOLO】【目标检测】交通事故识别数据集 8939 张,YOLO道路事故目标检测实战训练教程!
前端·人工智能·深度学习·yolo·目标检测·计算机视觉·pyqt
cs_dn_Jie5 小时前
钉钉 H5 微应用 手机端调试
前端·javascript·vue.js·vue·钉钉
开心工作室_kaic5 小时前
ssm068海鲜自助餐厅系统+vue(论文+源码)_kaic
前端·javascript·vue.js
有梦想的刺儿6 小时前
webWorker基本用法
前端·javascript·vue.js