如何将现有的回调API转换为Promise?

我想使用Promise,但我有一个回调API,格式如下:

1. DOM加载或其他一次性事件:

window.onload; // set to callback...window.onload = function() {
};

2.纯回调:

function request(onChangeHandler) {...}request(function() {// change happened...});

3.节点样式回调(“nodeback”):

function getStuff(dat, callback) {...}getStuff("dataParam", function(err, data) {...})

4.具有节点样式回调的整个库:

API;API.one(function(err, data) {API.two(function(err, data2) {API.three(function(err, data3) {...});});});

我如何在Promise中使用API,我如何“promisify”它?

302496 次浏览

Promise有状态,它们从挂起开始,可以解决:

  • 履行表示计算成功完成。
  • 拒绝表示计算失败。

Promise返回函数不应该扔,它们应该返回拒绝。从Promise返回函数抛出将迫使您同时使用} catch {.catch。使用promisiated API的人不希望抛出Promise。如果您不确定异步API在JS中的工作方式-请先看到这个答案

1. DOM加载或其他一次性事件:

因此,创建Promise通常意味着指定它们何时结算-这意味着当它们移动到已完成或已拒绝阶段以指示数据可用(并且可以使用.then访问)时。

使用支持Promise构造函数的现代Promise实现,例如本机ES6 Promise:

function load() {return new Promise(function(resolve, reject) {window.onload = resolve;});}

然后你会像这样使用生成的Promise:

load().then(function() {// Do things after onload});

使用支持延迟的库(让我们在这个例子中使用$q,但我们稍后也会使用jQuery):

function load() {var d = $q.defer();window.onload = function() { d.resolve(); };return d.promise;}

或者使用像API这样的jQuery,钩住一次发生的事件:

function done() {var d = $.Deferred();$("#myObject").once("click",function() {d.resolve();});return d.promise();}

2.纯回调:

这些API相当常见,因为回调在JS中很常见。让我们看看onSuccessonFail的常见情况:

function getUserData(userId, onLoad, onFail) { …

使用支持Promise构造函数的现代Promise实现,例如本机ES6 Promise:

function getUserDataAsync(userId) {return new Promise(function(resolve, reject) {getUserData(userId, resolve, reject);});}

使用支持延迟的库(让我们在这个例子中使用jQuery,但我们也在上面使用了$q):

function getUserDataAsync(userId) {var d = $.Deferred();getUserData(userId, function(res){ d.resolve(res); }, function(err){ d.reject(err); });return d.promise();}

jQuery还提供了一个$.Deferred(fn)表单,它的优点是允许我们编写一个非常接近new Promise(fn)表单的表达式,如下所示:

function getUserDataAsync(userId) {return $.Deferred(function(dfrd) {getUserData(userId, dfrd.resolve, dfrd.reject);}).promise();}

注意:这里我们利用了jQuery延迟的resolvereject方法是“可分离的”这一事实;即。它们绑定到jQuery. Deferred()的实例。并非所有库都提供此功能。

3.节点样式回调(“nodeback”):

节点样式回调(noDebacks)有一种特定的格式,其中回调始终是最后一个参数,其第一个参数是错误。让我们首先手动预测一个:

getStuff("dataParam", function(err, data) { …

致:

function getStuffAsync(param) {return new Promise(function(resolve, reject) {getStuff(param, function(err, data) {if (err !== null) reject(err);else resolve(data);});});}

使用deferreds,您可以执行以下操作(在此示例中使用Q,尽管Q现在支持新语法你应该更喜欢):

function getStuffAsync(param) {var d = Q.defer();getStuff(param, function(err, data) {if (err !== null) d.reject(err);else d.resolve(data);});return d.promise;}

一般来说,您不应该过多地手动Promisify东西,大多数在设计时考虑到Node以及Node 8+中的本机Promise的Promise库都有一个内置的方法来预测nodeack。例如

var getStuffAsync = Promise.promisify(getStuff); // Bluebirdvar getStuffAsync = Q.denodeify(getStuff); // Qvar getStuffAsync = util.promisify(getStuff); // Native promises, node only

4.具有节点样式回调的整个库:

这里没有黄金法则,你可以一个接一个地预测它们。但是,一些Promise实现允许你批量执行此操作,例如在Bluebird中,将nodeback API转换为Promise API就像以下操作一样简单:

Promise.promisifyAll(API);

或者节点中的原生承诺

const { promisify } = require('util');const promiseAPI = Object.entries(API).map(([key, v]) => ({key, fn: promisify(v)})).reduce((o, p) => Object.assign(o, {[p.key]: p.fn}), {});

备注:

  • 当然,当你在.then处理程序中时,你不需要promisify东西。从.then处理程序返回一个Promise将使用该Promise的值解析或拒绝。从.then处理程序抛出也是一种很好的做法,会拒绝Promise-这就是著名的Promise抛出安全。
  • 在实际的onload情况下,您应该使用addEventListener而不是onX

我不认为@Benjamin的window.onload建议会一直有效,因为它无法检测是否在加载后调用。我被咬了很多次。这是一个应该总是有效的版本:

function promiseDOMready() {return new Promise(function(resolve) {if (document.readyState === "complete") return resolve();document.addEventListener("DOMContentLoaded", resolve);});}promiseDOMready().then(initOnLoad);

kriskowal的Q库包括回调到承诺函数。像这样的方法:

obj.prototype.dosomething(params, cb) {...blah blah...cb(error, results);}

可以转换为Q.ninvoke

Q.ninvoke(obj,"dosomething",params).then(function(results) {});

您可以将JavaScript原生Promise与Node JS一起使用。

我的云9代码链接:https://ide.c9.io/adx2803/native-promises-in-node

/*** Created by dixit-lab on 20/6/16.*/
var express = require('express');var request = require('request');   //Simplified HTTP request client.

var app = express();
function promisify(url) {return new Promise(function (resolve, reject) {request.get(url, function (error, response, body) {if (!error && response.statusCode == 200) {resolve(body);}else {reject(error);}})});}
//get all the albums of a user who have posted post 100app.get('/listAlbums', function (req, res) {//get the post with post id 100promisify('http://jsonplaceholder.typicode.com/posts/100').then(function (result) {var obj = JSON.parse(result);return promisify('http://jsonplaceholder.typicode.com/users/' + obj.userId + '/albums')}).catch(function (e) {console.log(e);}).then(function (result) {res.end(result);})})
var server = app.listen(8081, function () {var host = server.address().addressvar port = server.address().port
console.log("Example app listening at http://%s:%s", host, port)})
//run webservice on browser : http://localhost:8081/listAlbums

当你有几个接受回调的函数并且你希望它们返回一个Promise时,你可以使用这个函数来进行转换。

function callbackToPromise(func){
return function(){
// change this to use what ever promise lib you are using// In this case i'm using angular $q that I exposed on a util module
var defered = util.$q.defer();
var cb = (val) => {defered.resolve(val);}
var args = Array.prototype.slice.call(arguments);args.push(cb);func.apply(this, args);
return defered.promise;}}

使用普通的旧vanilla javaScript,这里有一个解决方案来promisify api回调。

function get(url, callback) {var xhr = new XMLHttpRequest();xhr.open('get', url);xhr.addEventListener('readystatechange', function () {if (xhr.readyState === 4) {if (xhr.status === 200) {console.log('successful ... should call callback ... ');callback(null, JSON.parse(xhr.responseText));} else {console.log('error ... callback with error data ... ');callback(xhr, null);}}});xhr.send();}
/*** @function promisify: convert api based callbacks to promises* @description takes in a factory function and promisifies it* @params {function} input function to promisify* @params {array} an array of inputs to the function to be promisified* @return {function} promisified function* */function promisify(fn) {return function () {var args = Array.prototype.slice.call(arguments);return new Promise(function(resolve, reject) {fn.apply(null, args.concat(function (err, result) {if (err) reject(err);else resolve(result);}));});}}
var get_promisified = promisify(get);var promise = get_promisified('some_url');promise.then(function (data) {// corresponds to the resolve functionconsole.log('successful operation: ', data);}, function (error) {console.log(error);});

今天,我可以使用Node.js中的Promise作为一个普通的JavaScript方法。

Promise的一个简单而基本的例子(用的方式):

平原 Javascript异步API代码:

function divisionAPI (number, divider, successCallback, errorCallback) {
if (divider == 0) {return errorCallback( new Error("Division by zero") )}
successCallback( number / divider )
}

Promise Javascript Async API代码:

function divisionAPI (number, divider) {
return new Promise(function (fulfilled, rejected) {
if (divider == 0) {return rejected( new Error("Division by zero") )}
fulfilled( number / divider )
})
}

(推荐访问这个美丽的源头

此外,Promise可以与ES7中的async\await一起使用,以使程序流等待fullfiled结果,如下所示:

function getName () {
return new Promise(function (fulfilled, rejected) {
var name = "John Doe";
// wait 3000 milliseconds before calling fulfilled() methodsetTimeout (function() {fulfilled( name )},3000)
})
}

async function foo () {
var name = await getName(); // awaits for a fulfilled result!
console.log(name); // the console writes "John Doe" after 3000 milliseconds
}

foo() // calling the foo() method to run the code

使用.then()方法使用相同代码的另一种用法

function getName () {
return new Promise(function (fulfilled, rejected) {
var name = "John Doe";
// wait 3000 milliseconds before calling fulfilled() methodsetTimeout (function() {fulfilled( name )},3000)
})
}

// the console writes "John Doe" after 3000 millisecondsgetName().then(function(name){ console.log(name) })

Promise也可以在任何基于react-native等Node.js平台上使用。

奖金:一个混合方法
(假设回调方法有两个参数作为错误和结果)

function divisionAPI (number, divider, callback) {
return new Promise(function (fulfilled, rejected) {
if (divider == 0) {let error = new Error("Division by zero")callback && callback( error )return rejected( error )}
let result = number / dividercallback && callback( null, result )fulfilled( result )
})
}

上述方法可以响应老式回调和Promise用法的结果。

希望这有帮助。

您可以在ES6中使用原生Promise,例如处理setTimeout:

enqueue(data) {
const queue = this;// returns the Promisereturn new Promise(function (resolve, reject) {setTimeout(()=> {queue.source.push(data);resolve(queue); //call native resolve when finish}, 10); // resolve() will be called in 10 ms});
}

在这个例子中,Promise没有失败的理由,所以reject()永远不会被调用。

在内置了Promise和async的节点v7.6+下:

// promisify.jslet promisify = fn => (...args) =>new Promise((resolve, reject) =>fn(...args, (err, result) => {if (err) return reject(err);return resolve(result);}));
module.exports = promisify;

如何使用:

let readdir = require('fs').readdir;let promisify = require('./promisify');let readdirP = promisify(readdir);
async function myAsyncFn(path) {let entries = await readdirP(path);return entries;}

在Node.js8.0.0的发布版本中,有一个新的实用程序util.promisify(我已经写了关于util.promisify的文章),它封装了任何函数的预测能力。

它与其他答案中建议的方法没有太大区别,但具有作为核心方法的优势,并且不需要额外的依赖项。

const fs = require('fs');const util = require('util');
const readFile = util.promisify(fs.readFile);

然后你有一个readFile方法返回一个本机Promise

readFile('./notes.txt').then(txt => console.log(txt)).catch(...);

Node.js8.0.0包括一个新的util.promisify() API,它允许将标准Node.js回调样式API包装在返回Promise的函数中。

const fs = require('fs');const util = require('util');
const readFile = util.promisify(fs.readFile);
readFile('/some/file').then((data) => { /* ... */ }).catch((err) => { /* ... */ });

改进对Promises的支持

回调样式函数总是这样(几乎所有node.js函数都是这种风格):

//fs.readdir(path[, options], callback)fs.readdir('mypath',(err,files)=>console.log(files))

这种风格有相同的特点:

  1. 回调函数由最后一个参数传递。

  2. 回调函数总是接受错误对象作为它的第一个参数。

因此,您可以编写一个函数来转换具有这种风格的函数,如下所示:

const R =require('ramda')
/*** A convenient function for handle error in callback function.* Accept two function res(resolve) and rej(reject) ,* return a wrap function that accept a list arguments,* the first argument as error, if error is null,* the res function will call,else the rej function.* @param {function} res the function which will call when no error throw* @param {function} rej the function which will call when  error occur* @return {function} return a function that accept a list arguments,* the first argument as error, if error is null, the res function* will call,else the rej function**/const checkErr = (res, rej) => (err, ...data) => R.ifElse(R.propEq('err', null),R.compose(res,R.prop('data')),R.compose(rej,R.prop('err')))({err, data})
/*** wrap the callback style function to Promise style function,* the callback style function must restrict by convention:* 1. the function must put the callback function where the last of arguments,* such as (arg1,arg2,arg3,arg...,callback)* 2. the callback function must call as callback(err,arg1,arg2,arg...)* @param {function} fun the callback style function to transform* @return {function} return the new function that will return a Promise,* while the origin function throw a error, the Promise will be Promise.reject(error),* while the origin function work fine, the Promise will be Promise.resolve(args: array),* the args is which callback function accept* */const toPromise = (fun) => (...args) => new Promise((res, rej) => R.apply(fun,R.append(checkErr(res, rej),args)))

为了更简洁,上面的例子使用了ramda.js.Ramda.js是一个很好的函数式编程库。在上面的代码中,我们使用了它的适用(像javascriptfunction.prototype.apply)和append(像javascriptfunction.prototype.push)。因此,我们现在可以将回调样式函数转换为Promise样式函数:

const {readdir} = require('fs')const readdirP = toPromise(readdir)readdir(Path).then((files) => console.log(files),(err) => console.log(err))

toPromise检查错误函数由狂暴库拥有,它是ramda.js的函数编程库分支(由我创建)。

希望这个答案对你有用。

在将函数转换为Node.JS

var request = require('request'); //http wrapped module
function requestWrapper(url, callback) {request.get(url, function (err, response) {if (err) {callback(err);}else{callback(null, response);}})}

requestWrapper(url, function (err, response) {console.log(err, response)})

转换后

var request = require('request');
function requestWrapper(url) {return new Promise(function (resolve, reject) { //returning promiserequest.get(url, function (err, response) {if (err) {reject(err); //promise reject}else{resolve(response); //promise resolve}})})}

requestWrapper('http://localhost:8080/promise_request/1').then(function(response){console.log(response) //resolve callback(success)}).catch(function(error){console.log(error) //reject callback(failure)})

如果您需要处理多个请求

var allRequests = [];allRequests.push(requestWrapper('http://localhost:8080/promise_request/1'))allRequests.push(requestWrapper('http://localhost:8080/promise_request/2'))allRequests.push(requestWrapper('http://localhost:8080/promise_request/5'))
Promise.all(allRequests).then(function (results) {console.log(results);//result will be array which contains each promise response}).catch(function (err) {console.log(err)});

在Node.js8中,你可以使用这个npm模块promisify对象方法在飞行

https://www.npmjs.com/package/doasync

它使用util.promisify代理使您的对象保持不变。留存也使用WeakMaps完成)。以下是一些示例:

对于对象:

const fs = require('fs');const doAsync = require('doasync');
doAsync(fs).readFile('package.json', 'utf8').then(result => {console.dir(JSON.parse(result), {colors: true});});

具有功能:

doAsync(request)('http://www.google.com').then(({body}) => {console.log(body);// ...});

您甚至可以使用原生callapply绑定一些上下文:

doAsync(myFunc).apply(context, params).then(result => { /*...*/ });

es6-promisify将基于回调的函数转换为基于Promise的函数。

const promisify = require('es6-promisify');
const promisedFn = promisify(callbackedFn, args);

参考:https://www.npmjs.com/package/es6-promisify

我的callback函数的promisify版本是P函数:

var P = function() {var self = this;var method = arguments[0];var params = Array.prototype.slice.call(arguments, 1);return new Promise((resolve, reject) => {if (method && typeof(method) == 'function') {params.push(function(err, state) {if (!err) return resolve(state)else return reject(err);});method.apply(self, params);} else return reject(new Error('not a function'));});}var callback = function(par, callback) {var rnd = Math.floor(Math.random() * 2) + 1;return rnd > 1 ? callback(null, par) : callback(new Error("trap"));}
callback("callback", (err, state) => err ? console.error(err) : console.log(state))callback("callback", (err, state) => err ? console.error(err) : console.log(state))callback("callback", (err, state) => err ? console.error(err) : console.log(state))callback("callback", (err, state) => err ? console.error(err) : console.log(state))
P(callback, "promise").then(v => console.log(v)).catch(e => console.error(e))P(callback, "promise").then(v => console.log(v)).catch(e => console.error(e))P(callback, "promise").then(v => console.log(v)).catch(e => console.error(e))P(callback, "promise").then(v => console.log(v)).catch(e => console.error(e))

P函数要求回调签名必须是callback(error,result)

你可以做这样的事

// @flow
const toPromise = (f: (any) => void) => {return new Promise<any>((resolve, reject) => {try {f((result) => {resolve(result)})} catch (e) {reject(e)}})}
export default toPromise

那就用它

async loadData() {const friends = await toPromise(FriendsManager.loadFriends)
console.log(friends)}

它晚了5年,但我想在这里发布我的promesify版本,它从回调API中获取功能并将它们变成承诺

const promesify = fn => {return (...params) => ({then: cbThen => ({catch: cbCatch => {fn(...params, cbThen, cbCatch);}})});};

看看这个简单的版本:https://gist.github.com/jdtorregrosas/aeee96dd07558a5d18db1ff02f31e21a

下面是如何将函数(回调API)转换为Promise的实现。

function promisify(functionToExec) {return function() {var array = Object.values(arguments);return new Promise((resolve, reject) => {array.push(resolve)try {functionToExec.apply(null, array);} catch (error) {reject(error)}})}}
// USE SCENARIO
function apiFunction (path, callback) { // Not a promise// Logic}
var promisedFunction = promisify(apiFunction);
promisedFunction('path').then(()=>{// Receive the result here (callback)})
// Or use it with await like thislet result = await promisedFunction('path');

我通常使用的一个简单的泛型函数。

const promisify = (fn, ...args) => {return new Promise((resolve, reject) => {fn(...args, (err, data) => {if (err) {return reject(err);}resolve(data);});});};

如何使用它

  • 函数promisify接受一个带有回调的函数:
   const cb = (result) => `The result is ${result}`;
const sum = (a, b, cb) => {const result = a + b;cb(result); // passing args to the callback function}

// using the utilpromise = promisify(sum, 3, 1, cb);promise.then(x => console.log(x)) // 4

你可能不是在寻找这个答案,但这将有助于理解可用utils的内部工作原理

也许已经回答了,但我通常是这样做的:

// given you've defined this `Future` fn somewhere:const Future = fn => {return new Promise((r,t) => fn(r,t))}
// define an eventFn that takes a promise `resolver`const eventFn = resolve => {// do event related closure actions here. When finally done, call `resolve()`something.oneventfired = e => {resolve(e)}}
// invoke eventFn in an `async` workflowFn using `Future`// to obtain a `promise` wrapperconst workflowFn = async () => {await Future(eventFn)}

特别是对于indexedDb事件包装器这样的事情来简化使用。

或者您可能会发现Future的这个变体更通用

class PromiseEx extends Promise {resolve(v,...a) {this.settled = true; this.settledValue = v;return(this.resolve_(v,...a))}reject(v,...a) {this.settled = false; this.settledValue = v;return(this.reject_(v,...a))}static Future(fn,...args) {let r,t,ft = new PromiseEx((r_,t_) => {r=r_;t=t_})ft.resolve_ = r; ft.reject_ = t; fn(ft,...args);return(ft)}}

亡灵一点,位这个链接可能是有用的……


TLDR;查看答案末尾的片段示例


写入/转换可以被称为期望的函数

cb(error,result)new Promise (...)格式


  • promiseToCB转换并导出先前编码为返回Promise的现有函数
  • cbToPromise转换并导出先前编码为调用最后一个参数(错误,结果)的现有函数
    • 如果包装函数提供超过1个结果,则结果将是一个结果数组
    • egcb(undefined,path,stat)--->resolve([path,stat])/cb(undefined,[path,stat])
  • asPromise允许您编写一个新函数以返回一个Promise,但它可以以任何方式调用
  • asCallback允许您编写一个新函数来调用cb(err,result),但它可以以任何方式调用

样本函数

每个样本取2个参数,并根据随机数解决/拒绝/错误。

Arg2也可以用于强制通过或失败。(查找“-通过”或“-失败”)。

包装现有功能

  • 将函数导出到当前的“this”(或使用promiseToCB(function myFunc(){},newThis);

promiseToCB(function sampleFunc1(arg1,arg2) {console.log("deciding:",arg1,arg2);return new Promise(function(resolve,reject){       
const timer = setTimeout(function(){reject([arg1,arg2,"ouch"].join("-"));},5000);           
setTimeout(function(){if (arg2.endsWith("-pass") || (!arg2.endsWith("-fail") && Math.random()<0.5)) {    
console.log("complete:",arg1,arg2);clearTimeout(timer);resolve([arg1,arg2,"all good"].join("-"));}},2000);        
});});    
cbToPromise('sampleFunc2',function someOtherName(arg1,arg2,cb) {console.log("deciding:",arg1,arg2);const timer = setTimeout(function(){cb([arg1,arg2,"ouch"].join("-"));},5000);       
setTimeout(function(){if (arg2.endsWith("-pass") || (!arg2.endsWith("-fail") && Math.random()<0.5)) {console.log("complete:",arg1,arg2);clearTimeout(timer);cb(undefined,[arg1,arg2,"all good"].join("-"));}},2000);        
},local);    

或编写嵌入包装器的新函数。

     function sampleFunc3(arg1,arg2) {return asPromise(arguments,function(resolve,reject){console.log("deciding:",arg1,arg2);const timer = setTimeout(function(){reject([arg1,arg2,"ouch"].join("-"));},5000);       
setTimeout(function(){if (arg2.endsWith("-pass") || (!arg2.endsWith("-fail") && Math.random()<0.5)) {console.log("complete:",arg1,arg2);clearTimeout(timer);resolve([arg1,arg2,"all good"].join("-"));}},2000);        
});}    
function sampleFunc4(arg1,arg2) {return asCallback(arguments,function(cb){console.log("deciding:",arg1,arg2);const timer = setTimeout(function(){cb([arg1,arg2,"ouch"].join("-"));},5000);       
setTimeout(function(){if (arg2.endsWith("-pass") || (!arg2.endsWith("-fail") && Math.random()<0.5)) {console.log("complete:",arg1,arg2);clearTimeout(timer);cb(undefined,[arg1,arg2,"all good"].join("-"));}},2000);        
});}

对上述功能进行测试

const local = {};promiseToCB(function sampleFunc1(arg1,arg2) {console.log("deciding:",arg1,arg2);return new Promise(function(resolve,reject){       
const timer = setTimeout(function(){reject([arg1,arg2,"ouch"].join("-"));},5000);           
setTimeout(function(){if (arg2.endsWith("-pass") || (!arg2.endsWith("-fail") && Math.random()<0.5)) {    
console.log("complete:",arg1,arg2);clearTimeout(timer);resolve([arg1,arg2,"all good"].join("-"));}},2000);        
});});    
cbToPromise('sampleFunc2',function someOtherName(arg1,arg2,cb) {console.log("deciding:",arg1,arg2);const timer = setTimeout(function(){cb([arg1,arg2,"ouch"].join("-"));},5000);       
setTimeout(function(){if (arg2.endsWith("-pass") || (!arg2.endsWith("-fail") && Math.random()<0.5)) {console.log("complete:",arg1,arg2);clearTimeout(timer);cb(undefined,[arg1,arg2,"all good"].join("-"));}},2000);        
},local);    
function sampleFunc3(arg1,arg2) {return asPromise(arguments,function(resolve,reject){console.log("deciding:",arg1,arg2);const timer = setTimeout(function(){reject([arg1,arg2,"ouch"].join("-"));},5000);       
setTimeout(function(){if (arg2.endsWith("-pass") || (!arg2.endsWith("-fail") && Math.random()<0.5)) {console.log("complete:",arg1,arg2);clearTimeout(timer);resolve([arg1,arg2,"all good"].join("-"));}},2000);        
});}    
function sampleFunc4(arg1,arg2) {return asCallback(arguments,function(cb){console.log("deciding:",arg1,arg2);const timer = setTimeout(function(){cb([arg1,arg2,"ouch"].join("-"));},5000);       
setTimeout(function(){if (arg2.endsWith("-pass") || (!arg2.endsWith("-fail") && Math.random()<0.5)) {console.log("complete:",arg1,arg2);clearTimeout(timer);cb(undefined,[arg1,arg2,"all good"].join("-"));}},2000);        
});}    
const log=console.log.bind(console),info=console.info.bind(console),error=console.error.bind(console);    
sampleFunc1("sample1","promise").then (log).catch(error);local.sampleFunc2("sample2","promise").then (log).catch(error);sampleFunc3("sample3","promise").then (log).catch(error);sampleFunc4("sample4","promise").then (log).catch(error);
sampleFunc1("sample1","callback",info);local.sampleFunc2("sample2","callback",info);sampleFunc3("sample3","callback",info);sampleFunc4("sample4","callback",info);    
sampleFunc1("sample1","promise-pass").then (log).catch(error);local.sampleFunc2("sample2","promise-pass").then (log).catch(error);sampleFunc3("sample3","promise-pass").then (log).catch(error);sampleFunc4("sample4","promise-pass").then (log).catch(error);
sampleFunc1("sample1","callback-pass",info);local.sampleFunc2("sample2","callback-pass",info);sampleFunc3("sample3","callback-pass",info);sampleFunc4("sample4","callback-pass",info);    
    
sampleFunc1("sample1","promise-fail").then (log).catch(error);local.sampleFunc2("sample2","promise-fail").then (log).catch(error);sampleFunc3("sample3","promise-fail").then (log).catch(error);sampleFunc4("sample4","promise-fail").then (log).catch(error);    
sampleFunc1("sample1","callback-fail",info);local.sampleFunc2("sample2","callback-fail",info);sampleFunc3("sample3","callback-fail",info);sampleFunc4("sample4","callback-fail",info); 

    var cpArgs = Array.prototype.slice.call.bind(Array.prototype.slice);
function promiseToCB (nm,fn,THIS) {if (typeof nm==='function') {THIS=fn;fn=nm;nm=fn.name;}THIS=THIS||this;const func = function () {let args = cpArgs(arguments);if (typeof args[args.length-1]==='function') {const cb = args.pop();return fn.apply(THIS,args).then(function(r){cb (undefined,r);}).catch(cb);} else {return fn.apply(THIS,args);}};Object.defineProperty(func,'name',{value:nm,enumerable:false,configurable: true});if (THIS[nm]) delete THIS[nm];Object.defineProperty(THIS,nm,{value:func,enumerable:false,configurable: true});return func;}
function cbToPromise (nm,fn,THIS) {if (typeof nm==='function') {THIS=fn;fn=nm;nm=fn.name;}THIS=THIS||this;const func = function () {let args = cpArgs(arguments);if (typeof args[args.length-1]==='function') {return fn.apply(THIS,args);} else {return new Promise(function(resolve,reject){                    
args.push(function(err,result){if (err) return reject(err);if (arguments.length==2) {return resolve(result);}return resolve(cpArgs(arguments,1));});                              
fn.apply(THIS,args);                    
});}};Object.defineProperty(func,'name',{value:nm,enumerable:false,configurable: true});if (THIS[nm]) delete THIS[nm];Object.defineProperty(THIS,nm,{value:func,enumerable:false,configurable: true});return func;
}
function asPromise (args,resolver,no_err) {const cb = args[args.length-1],promise  = new Promise(resolver);return (typeof cb==='function')  ? promise.then(function(result){return cb(no_err,result)}).catch(cb) : promise;}
function asCallback (args,wrap,no_err) {const cb = args[args.length-1],promise=new Promise(function resolver(resolve,reject) {return wrap (function (err,result) {if (err) return reject(err);resolve(result);});});return (typeof cb==='function')  ? promise.then(function(result){return cb(no_err,result)}).catch(cb) : promise;}

function cbPromiseTest(){/*global sampleFunc1,sampleFunc2*/        
const local = {};promiseToCB(function sampleFunc1(arg1,arg2) {console.log("deciding:",arg1,arg2);return new Promise(function(resolve,reject){           
const timer = setTimeout(function(){reject([arg1,arg2,"ouch"].join("-"));},5000);               
setTimeout(function(){if (arg2.endsWith("-pass") || (!arg2.endsWith("-fail") && Math.random()<0.5)) {        
console.log("complete:",arg1,arg2);clearTimeout(timer);resolve([arg1,arg2,"all good"].join("-"));}},2000);            
});});        
cbToPromise('sampleFunc2',function someOtherName(arg1,arg2,cb) {console.log("deciding:",arg1,arg2);const timer = setTimeout(function(){cb([arg1,arg2,"ouch"].join("-"));},5000);           
setTimeout(function(){if (arg2.endsWith("-pass") || (!arg2.endsWith("-fail") && Math.random()<0.5)) {console.log("complete:",arg1,arg2);clearTimeout(timer);cb(undefined,[arg1,arg2,"all good"].join("-"));}},2000);            
},local);        
function sampleFunc3(arg1,arg2) {return asPromise(arguments,function(resolve,reject){console.log("deciding:",arg1,arg2);const timer = setTimeout(function(){reject([arg1,arg2,"ouch"].join("-"));},5000);           
setTimeout(function(){if (arg2.endsWith("-pass") || (!arg2.endsWith("-fail") && Math.random()<0.5)) {console.log("complete:",arg1,arg2);clearTimeout(timer);resolve([arg1,arg2,"all good"].join("-"));}},2000);            
});}        
function sampleFunc4(arg1,arg2) {return asCallback(arguments,function(cb){console.log("deciding:",arg1,arg2);const timer = setTimeout(function(){cb([arg1,arg2,"ouch"].join("-"));},5000);           
setTimeout(function(){if (arg2.endsWith("-pass") || (!arg2.endsWith("-fail") && Math.random()<0.5)) {console.log("complete:",arg1,arg2);clearTimeout(timer);cb(undefined,[arg1,arg2,"all good"].join("-"));}},2000);            
});}        
const log=console.log.bind(console),info=console.info.bind(console),error=console.error.bind(console);        
sampleFunc1("sample1","promise").then (log).catch(error);local.sampleFunc2("sample2","promise").then (log).catch(error);sampleFunc3("sample3","promise").then (log).catch(error);sampleFunc4("sample4","promise").then (log).catch(error);
sampleFunc1("sample1","callback",info);local.sampleFunc2("sample2","callback",info);sampleFunc3("sample3","callback",info);sampleFunc4("sample4","callback",info);        
sampleFunc1("sample1","promise-pass").then (log).catch(error);local.sampleFunc2("sample2","promise-pass").then (log).catch(error);sampleFunc3("sample3","promise-pass").then (log).catch(error);sampleFunc4("sample4","promise-pass").then (log).catch(error);
sampleFunc1("sample1","callback-pass",info);local.sampleFunc2("sample2","callback-pass",info);sampleFunc3("sample3","callback-pass",info);sampleFunc4("sample4","callback-pass",info);        
        
sampleFunc1("sample1","promise-fail").then (log).catch(error);local.sampleFunc2("sample2","promise-fail").then (log).catch(error);sampleFunc3("sample3","promise-fail").then (log).catch(error);sampleFunc4("sample4","promise-fail").then (log).catch(error);        
sampleFunc1("sample1","callback-fail",info);local.sampleFunc2("sample2","callback-fail",info);sampleFunc3("sample3","callback-fail",info);sampleFunc4("sample4","callback-fail",info);     
}cbPromiseTest();

Promise总是有一个resolve和一个reject。当你编写一个异步包装器时,只需调用解析就可以了。

您可以为几乎任何接受回调的函数编写包装函数,如下所示:

const myAsyncWrapper = (...params) =>new Promise((resolve, reject) =>someFunctionWithCallback(...params, (error, response) =>error ? reject(error) : resolve(response)));

您可以进一步编写回调到Promise的转换函数:

const promisify =(functionWithCallback) =>(...params) =>new Promise((resolve, reject) =>functionWithCallback(...params, (error, response) =>error ? reject(error) : resolve(response)));

这种包装函数的概念在使用较旧的库或SDK时特别有用。例如,考虑Facebook Graph API的JavaScript SDK,它使用类似的回调结构来发出API请求。

FB.api(apiURL, options, function (request) {if (request.error || !request) return;// handle request});

在现代应用程序中,使用基于Promise的API更有用。如果你只使用一个函数一次或两次,它可能是更好的单独promisify响应:

// in an async functionconst response = await new Promise((resolve, reject) =>FB.api(apiURL, (res) => (res?.error ? reject(res?.error) : resolve(res))));

如果你经常使用这个函数,你可以使用相同的包装器概念来编写一个函数,如下所示:

const apiWrapper = (...params) =>new Promise((resolve, reject) =>FB.api(...params, (res) => (res?.error ? reject(res?.error) : resolve(res))));

尽管promisiader有时很棒,但它们不适用于像这样的特定实例。在这种情况下,在Github上寻找现代包装器,或者像这样编写你自己的。

因为我们事先知道基于回调的函数的特征,我们可以创建一个函数来转换基于回调的函数返回一个Promise的等效函数。

  • 回调是函数的最后一个参数

  • 如果有错误,它总是传递给回调的第一个参数

  • 任何返回值在错误后传递给回调

     function promisify(yourCallbackApi) {return function promisified(...args) {return new Promise((resolve, reject) => {// newArgs=[..args,callback]const newArgs = [...args,function (err, result) {if (err) {return reject(err);}resolve(result);},];// invoke yourCallbackApi with the new list of argumentsyourCallbackApi(...newArgs);});};}