相信你们都知道Node 8提供了两个工具函数util.promisify, util.callbackify用于在回调函数和promise之间作方便的切换, 今天咱们讲下他们简单的实现。由于到了浏览器你就不能直接用node的函数啦,固然npm有一些包可使用。
下面Node提供的例子,简单易懂javascript
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抛出去:java
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写法:node
fn(...args, (err, value) => {}) {}git
const promisify = fnWithStandardCallback => (...args) => new Promise((resolve, reject) => { fnWithStandardCallback(...args, (err, info) => { if (err) { reject(err); } else { resolve(info); } }); }); // 使用 const convertedStat = promisify(fs.stat);复制代码
为了有些童鞋可能看不清楚太多的箭头函数,附上更清楚的版本:github
const promisify = fnWithStandardCallback => { return (...args) => { return new Promise((resolve, reject) => { fnWithStandardCallback(...args, (err, info) => { if (err) { reject(err); } else { resolve(info); } }); }); }; };复制代码
除了上面标准的callback外层函数写法外,另外一种写法以下也比较常见:npm
fn(...args, onSuccess, onError) {}promise
onSuccess/onError都是 fn(value) {}样子的回调函数,这种写法没有把成功/失败放在一个callback里而是分开处理。浏览器
Node对非标准的状况处理是:添加了一个Symbol叫作util.promisify.custom,而后你在调用promisify方法前自定义你的返回逻辑。本质上就是上面咱们具体例子具体实现的版本:markdown
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.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加上这个中间层,由于其实改动的成本差很少。但总有各类各样的状况,好比你的回调函数已经有不少地方使用了,牵一发而动全身,这时这个中间层仍是比较有用的。
引用: