相信你們都知道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加上這個中間層,由於其實改動的成本差很少。但總有各類各樣的狀況,好比你的回調函數已經有不少地方使用了,牽一髮而動全身,這時這個中間層仍是比較有用的。
引用: