相信大家都知道Node 8提供了两个工具函数util.promisify, util.callbackify用于在回调函数和promise之间做方便的切换, 今天我们讲下他们简单的实现。因为到了浏览器你就不能直接用node的函数啦,当然npm有一些包可以使用。
回顾用法
下面Node提供的例子,简单易懂
const util = require('util');
const fs = require('fs');
// stat方法用来查看文件信息的,定义:fs.stat(path, callback), 原用法如下
// fs.stat('.', (err, stats) => {
// if (err) { // handle error }
// // 处理取到的信息
// })
const stat = util.promisify(fs.stat);
stat('.').then((stats) => {
// Do something with `stats`
}).catch((error) => {
// Handle the error.
});
方便起见,我们假设前端能直接用fs.stat方法,在不能用util.promisify的情况下我们怎么把他转化成promise?
具体例子具体实现
中心思想无非就是callback出结果后把相应的结果/错误用Promise.resolve/reject抛出去:
const stat = path => new Promise((resolve, reject) => {
fs.stat(path, (err, info) => {
if (err) {
reject(err);
} else {
resolve(info);
}
});
});
// 使用1
stat('.').then(info =>{}).catch(err => {});
// 使用2 async语法
const useFunc = async () => {
try {
const info = await stat('.');
return info;
} catch(err) {
console.error('报错啦', err);
}
}
useFunc();
通用实现
为了以后重用,我们自己实现一个promisify,他接收函数是标准的node写法:
fn(...args, (err, value) => {}) {}
const promisify = fnWithStandardCallback => (...args) => new Promise((resolve, reject) => {
fnWithStandardCallback(...args, (err, info) => {
if (err) {
reject(err);
} else {
resolve(info);
}
});
});
// 使用
const convertedStat = promisify(fs.stat);
为了有些童鞋可能看不清楚太多的箭头函数,附上更清楚的版本:
const promisify = fnWithStandardCallback => {
return (...args) => {
return new Promise((resolve, reject) => {
fnWithStandardCallback(...args, (err, info) => {
if (err) {
reject(err);
} else {
resolve(info);
}
});
});
};
};
Node对另一种callback的处理
除了上面标准的callback外层函数写法外,另一种写法如下也比较常见:
fn(...args, onSuccess, onError) {}
onSuccess/onError都是 fn(value) {}样子的回调函数,这种写法没有把成功/失败放在一个callback里而是分开处理。
Node对非标准的情况处理是:添加了一个Symbol叫做util.promisify.custom,然后你在调用promisify方法前自定义你的返回逻辑。本质上就是上面我们具体例子具体实现的版本:
const { promisify } = require('util');
const isTruthy = (value, onSuccess, onError) => {
if (value) {
onSuccess(value);
} else {
onError(value);
}
};
isTruthy[promisify.custom] = value => new Promise((resove, reject) => {
isTruthy(value, () => {
resolve(value);
}, () => {
reject(value);
});
});
promisify(isTruthy)(true).then(() => {});
改动我们的版本
const CUSTOM = Symbol('mypromisify.custom');
const promisify = fnWithStandardCallback => {
if (fnWithStandardCallback[CUSTOM]) {
// 有自定义?直接返回你自定义的版本
return fnWithStandardCallback[CUSTOM];
}
return (...args) => {
return new Promise((resolve, reject) => {
fnWithStandardCallback(...args, (err, info) => {
if (err) {
reject(err);
} else {
resolve(info);
}
});
});
};
};
promise.custom = CUSTOM; // 方便用户调用,不然没人记得那个symbol是啥
把promise转成callback形式
这种转换几乎不会用到,我们简单讲下实现:promise.then/catch完之后,再调用用户提供的callback函数即可
const callbackify = (fnThatReturnsPromise) => (...args) => {
// 按照约定,最后一个参数为回调
const callback = args[args.length - 1];
// 真正传给原方法的参数
const otherArgs = args.slice(0, -1);
fnThatReturnsPromise(...otherArgs).then(info => {
callback(null, info);
}).catch(err => {
callback(err);
});
}
个人而言,我是赞成直接把代码改成promise形式的,而不是对已有的callback加上这个中间层,因为其实改动的成本差不多。但总有各种各样的情况,比如你的回调函数已经有很多地方使用了,牵一发而动全身,这时这个中间层还是比较有用的。
引用: