如何将现有的回调 API 转换为 Promise?
Posted
技术标签:
【中文标题】如何将现有的回调 API 转换为 Promise?【英文标题】:How do I convert an existing callback API to promises? 【发布时间】:2022-01-24 04:23:24 【问题描述】:我想使用 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”它?
【问题讨论】:
我发布了我自己的答案,但答案扩展了如何为特定库或在更多情况下执行此操作,并且非常欢迎编辑。 @Bergi 这是一个有趣的想法,我尝试使用两种常用方法(Promise 构造函数和延迟对象)做出一个通用答案。我试图在答案中给出这两种选择。我同意 RTFMing 解决了这个问题,但我们经常在这里和错误跟踪器中遇到这个问题,所以我认为有一个“规范问题”——我认为 RTFMing 解决了 JS 标签中大约 50% 的问题:D 如果您有一个有趣的见解可以在答案或编辑中做出贡献,我们将不胜感激。 创建new Promise
是否会增加任何重大开销?我想将我所有的同步 Noje.js 函数包装在一个 Promise 中,以便从我的 Node 应用程序中删除所有同步代码,但这是最佳实践吗?换句话说,一个接受静态参数(例如字符串)并返回计算结果的函数,我应该将其包装在 Promise 中吗? ...我在某处读到,Nodejs 中不应该有任何同步代码。
@RonRoyston 不,用 Promise 包装同步调用不是一个好主意——只有可以执行 I/O 的异步调用
【参考方案1】:
Promise 总是有一个resolve
和一个reject
。当您编写异步包装器时,只需调用 resolve 即可。
您可以为几乎任何接受回调的函数编写一个包装函数,如下所示:
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 更为有用。 如果您只使用一次或两次函数,最好单独承诺响应:
// in an async function
const 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)))
);
虽然承诺者有时很棒,但它们不适用于像这样的特定情况。在这种情况下,在 Github 上寻找现代包装器,或者像这样编写自己的包装器。
【讨论】:
【参考方案2】:我常用的一个简单的泛型函数。
const promisify = (fn, ...args) =>
return new Promise((resolve, reject) =>
fn(...args, (err, data) =>
if (err)
return reject(err);
resolve(data);
);
);
;
如何使用
函数promisify
接受带有回调的函数:
const cb = (x) => x;
const sum = (a, b, cb) =>
cb(null, a + b)
// using the util
promise = promisify(sum, 3, 1);
promise.then(x => console.log(x)) // 4
您可能不是在寻找这个答案,但这将有助于了解可用工具的内部工作原理
【讨论】:
我正在尝试使用它,但如果我调用promisify(fn, arg1, arg2).then(() => alert("Done!"); );
,则永远不会触发警报。你希望这能奏效吗?
谢谢@Philip Stratford 的提问。 promisify
用于将带有回调的函数转换为 Promise。我会更新我的答案来解释这一点。
我很高兴听到有关此解决方案的任何建议,抄送@Philip Stratford。谢谢【参考方案3】:
通灵一点,这个link可能有用....
TLDR;看看这个答案末尾的 sn-p 示例
编写/转换可以调用的函数
cb(error,result)
或 new Promise (...)
格式
promiseToCB
转换和导出先前已编码为返回承诺的现有函数
cbToPromise
转换并导出一个现有函数,该函数先前已编码为使用 (error,result) 调用最后一个参数
如果包装函数提供超过 1 个结果,则结果将是结果数组
例如cb(undefined,path,stat)
---> resolve([path,stat])
/ cb(undefined,[path,stat])
asPromise
允许您编写一个新函数来返回一个承诺,但它可以以任何一种方式调用
asCallback
允许您编写一个新函数来调用 cb(err,result)
,但它可以通过任何一种方式调用
示例函数
每个样本有 2 个参数,并根据随机数解决/拒绝/错误。
arg2 也可用于强制通过或失败。 (寻找“-pass”或“-fail”)。
包装现有函数
将函数导出到当前的“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();
【讨论】:
【参考方案4】:也许已经回答了,但我通常是这样回答的:
// 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` wrapper
const 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)
【讨论】:
【参考方案5】:Promise 有状态,它们以待处理状态开始并且可以解决:
fulfilled 表示计算成功完成。 rejected 表示计算失败。Promise 返回函数 should never throw,它们应该返回拒绝。从承诺返回函数中抛出将迫使您同时使用 catch
和 .catch
。使用 Promisified API 的人并不期望 Promise 会抛出。如果您不确定异步 API 在 JS 中是如何工作的 - 请先see this answer。
1。 DOM 加载或其他一次性事件:
因此,创建 Promise 通常意味着指定它们何时结算 - 这意味着它们何时进入已完成或已拒绝阶段以指示数据可用(并且可以通过 .then
访问)。
使用支持 Promise
构造函数的现代 Promise 实现,例如原生 ES6 Promise:
function load()
return new Promise(function(resolve, reject)
window.onload = resolve;
);
然后你会像这样使用生成的承诺:
load().then(function()
// Do things after onload
);
使用支持延迟的库(我们在此示例中使用 $q,但稍后我们还将使用 jQuery):
function load()
var d = $q.defer();
window.onload = function() d.resolve(); ;
return d.promise;
或者使用类似 jQuery 的 API,挂钩一次发生的事件:
function done()
var d = $.Deferred();
$("#myObject").once("click",function()
d.resolve();
);
return d.promise();
2。普通回调:
这些 API 相当普遍,因为……回调在 JS 中很常见。让我们看一下拥有onSuccess
和onFail
的常见情况:
function getUserData(userId, onLoad, onFail) …
使用支持 Promise
构造函数的现代承诺实现,如原生 ES6 承诺:
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 deferred 的 resolve
和 reject
方法是“可分离的”这一事实; IE。它们绑定到 jQuery.Deferred() 的 instance。并非所有库都提供此功能。
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);
);
);
使用延迟,您可以执行以下操作(我们在此示例中使用 Q,尽管 Q 现在支持新语法 which you should prefer):
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;
一般来说,您不应该过多地手动承诺事情,大多数在设计时考虑到 Node 的承诺库以及 Node 8+ 中的本机承诺都有一个用于承诺 nodebacks 的内置方法。例如
var getStuffAsync = Promise.promisify(getStuff); // Bluebird
var getStuffAsync = Q.denodeify(getStuff); // Q
var getStuffAsync = util.promisify(getStuff); // Native promises, node only
4。带有节点样式回调的整个库:
这里没有金科玉律,你一一答应他们。但是,一些 Promise 实现允许您批量执行此操作,例如在 Bluebird 中,将 nodeback API 转换为 Promise API 非常简单:
Promise.promisifyAll(API);
或者在 Node 中使用 native promises:
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
处理程序中时,您不需要承诺任何事情。从.then
处理程序返回一个promise 将使用该promise 的值解析或拒绝。从.then
处理程序投掷也是一种很好的做法,并且会拒绝承诺 - 这是著名的承诺投掷安全。
在实际的onload
情况下,您应该使用addEventListener
而不是onX
。
【讨论】:
Benjamin,我接受了您的编辑邀请,并在案例 2 中添加了另一个 jQuery 示例。它需要经过同行评审才能出现。希望你喜欢。 @Roamer-1888 它被拒绝了,因为我没有及时看到并接受它。对于它的价值,我认为添加虽然有用,但并不太相关。 Benjamin,无论resolve()
和 reject()
是否被编写为可重用,我敢说我建议的编辑是相关的,因为它提供了一个 $.Deferred(fn)
形式的 jQuery 示例,否则不足。如果只包含一个 jQuery 示例,那么我建议它应该是这种形式而不是 var d = $.Deferred();
等,因为应该鼓励人们使用经常被忽视的 $.Deferred(fn)
形式,另外,在这样的答案中,它提出jQuery 更接近于使用 Revealing Constructor Pattern 的库。
嘿,100% 公平地说,我不知道 jQuery 会让你做$.Deferred(fn)
,如果你在接下来的 15 分钟内编辑它而不是现有示例,我相信我可以尝试准时批准:)
这是一个很好的答案。您可能还想通过提及util.promisify
来更新它,Node.js 将从 RC 8.0.0 开始添加到其核心。它的工作方式与 Bluebird Promise.promisify
没有太大区别,但它的优点是不需要额外的依赖项,以防你只需要原生 Promise。我写了一篇关于 util.promisify 的博文,供任何想了解更多有关该主题的人使用。【参考方案6】:
以下是如何将函数(回调 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 this
let result = await promisedFunction('path');
【讨论】:
【参考方案7】:Node.js 8.0.0 包含一个新的util.promisify()
API,它允许将标准 Node.js 回调样式 API 包装在返回 Promise 的函数中。 util.promisify()
的使用示例如下所示。
const fs = require('fs');
const util = require('util');
const readFile = util.promisify(fs.readFile);
readFile('/some/file')
.then((data) => /** ... **/ )
.catch((err) => /** ... **/ );
见Improved support for Promises
【讨论】:
已经有两个答案描述了这个,为什么还要发布第三个? 只是因为那个版本的node现在发布了,我已经报告了“官方”的功能描述和链接。 @BenjaminGruenbaum 我对此表示赞同,因为它不那么“杂乱”且有效。顶部的那个有很多其他的东西,以至于答案丢失了。【参考方案8】:在 Node.JS 中将函数转换为 Promise 之前
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 promise
request.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)
);
【讨论】:
【参考方案9】:好像晚了 5 年,但我想在这里发布我的 promesify 版本,它从回调 API 中获取函数并将它们转换为 Promise
const promesify = fn =>
return (...params) => (
then: cbThen => (
catch: cbCatch =>
fn(...params, cbThen, cbCatch);
)
);
;
在这里查看这个非常简单的版本: https://gist.github.com/jdtorregrosas/aeee96dd07558a5d18db1ff02f31e21a
【讨论】:
这不是一个承诺,它不会链接,处理回调中抛出的错误或接受第二个参数...【参考方案10】:今天,我可以在Node.js
中使用Promise
作为纯Javascript 方法。
Promise
的一个简单而基本的例子(用 KISS 方式):
普通 Javascript Async API 代码:
function divisionAPI (number, divider, successCallback, errorCallback)
if (divider == 0)
return errorCallback( new Error("Division by zero") )
successCallback( number / divider )
Promise
Javascript 异步 API 代码:
function divisionAPI (number, divider)
return new Promise(function (fulfilled, rejected)
if (divider == 0)
return rejected( new Error("Division by zero") )
fulfilled( number / divider )
)
(我推荐访问this beautiful source)
还可以将Promise
与ES7
中的async\await
一起使用,以使程序流等待fullfiled
的结果,如下所示:
function getName ()
return new Promise(function (fulfilled, rejected)
var name = "John Doe";
// wait 3000 milliseconds before calling fulfilled() method
setTimeout (
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() method
setTimeout (
function()
fulfilled( name )
,
3000
)
)
// the console writes "John Doe" after 3000 milliseconds
getName().then(function(name) console.log(name) )
Promise
也可以在任何基于 Node.js 的平台上使用,例如 react-native
。
奖励:一种混合方法 (回调方法假设有error和result两个参数)
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 / divider
callback && callback( null, result )
fulfilled( result )
)
上述方法可以响应老式回调和 Promise 用法的结果。
希望这会有所帮助。
【讨论】:
这些似乎没有显示如何转换为承诺。【参考方案11】:你可以这样做
// @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)
【讨论】:
嘿,我不确定这会给现有答案增加什么(也许澄清一下?)。此外,promise 构造函数中不需要 try/catch(它会自动为您执行此操作)。还不清楚这适用于哪些功能(在成功时使用单个参数调用回调?如何处理错误?)【参考方案12】: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)
。
【讨论】:
与原生 promisify 或上述答案相比,这有什么优势? 原生 Promisify 是什么意思?util.promisify(fn)
啊,当然:)。只是和例子来展示基本的想法。事实上,您可以看到即使是原生的也要求函数签名必须像(err, value) => ...
那样定义,或者您必须定义一个自定义的签名(请参阅自定义承诺函数)。谢谢你的好消息。
@loretoparisi 仅供参考,var P = function (fn, ...args) return new Promise((resolve, reject) => fn.call(this, ...args, (error, result) => error ? reject(error) : resolve(result))); ;
会做和你一样的事情,而且要简单得多。【参考方案13】:
es6-promisify
将基于回调的函数转换为基于 Promise 的函数。
const promisify = require('es6-promisify');
const promisedFn = promisify(callbackedFn, args);
参考:https://www.npmjs.com/package/es6-promisify
【讨论】:
【参考方案14】:在 Node.js 8 中,您可以使用此 npm 模块promisify动态:
https://www.npmjs.com/package/doasync
它使用 util.promisify 和 Proxies 使您的对象保持不变。 Memoization 也是使用 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);
// ...
);
你甚至可以使用原生的call
和apply
来绑定一些上下文:
doAsync(myFunc).apply(context, params)
.then(result => /*...*/ );
【讨论】:
【参考方案15】:回调风格函数总是这样(node.js中几乎所有函数都是这种风格):
//fs.readdir(path[, options], callback)
fs.readdir('mypath',(err,files)=>console.log(files))
这种风格有相同的特点:
回调函数由最后一个参数传递。
回调函数总是接受错误对象作为它的第一个参数。
因此,您可以编写一个函数来转换具有这种风格的函数,如下所示:
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 是一个优秀的函数式编程库。在上面的代码中,我们使用了 apply(如 javascript function.prototype.apply
)和 append(如 javascript function.prototype.push
)。
因此,我们现在可以将回调样式函数转换为 Promise 样式函数:
const readdir = require('fs')
const readdirP = toPromise(readdir)
readdir(Path)
.then(
(files) => console.log(files),
(err) => console.log(err)
)
toPromise 和 checkErr 函数由 berserk 库拥有,它是 ramda.js 的函数式编程库分支(由我创建)。
希望这个答案对你有用。
【讨论】:
【参考方案16】:在 Node.js 8.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(...);
【讨论】:
嘿,我(OP)实际上两次建议util.promisify
(早在 2014 年写这个问题的时候,几个月前 - 我作为 Node 的核心成员推动了它并且是当前我们在 Node 中的版本)。由于它尚未公开 - 我尚未将其添加到此答案中。不过,我们将非常感谢使用反馈,并了解一些陷阱以便为该版本提供更好的文档:)
此外,您可能想在您的博客文章中讨论用于承诺的自定义标志 util.promisify
:)
@BenjaminGruenbaum 你的意思是说使用util.promisify.custom
符号可以覆盖util.promisify 的结果吗?老实说,这是一个故意的失误,因为我还没有找到一个有用的用例。也许你可以给我一些意见?
当然,考虑像 fs.exists
这样的 API 或不遵循 Node 约定的 API - 蓝鸟 Promise.promisify
会误会它们,但 util.promisify
会正确。【参考方案17】:
在 node v7.6+ 下,内置了 Promise 和 async:
// promisify.js
let 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;
【讨论】:
【参考方案18】:您可以在 ES6 中使用 native Promise,例如处理 setTimeout:
enqueue(data)
const queue = this;
// returns the Promise
return 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()
永远不会被调用。
【讨论】:
【参考方案19】:使用普通的老式 javaScript,这是一个承诺 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 function
console.log('successful operation: ', data);
, function (error)
console.log(error);
);
【讨论】:
【参考方案20】:当您有几个函数需要回调并且您希望它们返回一个 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;
【讨论】:
【参考方案21】:您可以在 Node JS 中使用 JavaScript 原生 Promise。
My Cloud 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 100
app.get('/listAlbums', function (req, res)
//get the post with post id 100
promisify('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().address
var port = server.address().port
console.log("Example app listening at http://%s:%s", host, port)
)
//run webservice on browser : http://localhost:8081/listAlbums
【讨论】:
【参考方案22】:kriskowal 的 Q 库包含回调到承诺的函数。 像这样的方法:
obj.prototype.dosomething(params, cb)
...blah blah...
cb(error, results);
可以用 Q.ninvoke 转换
Q.ninvoke(obj,"dosomething",params).
then(function(results)
);
【讨论】:
规范答案已经提到Q.denodeify
。我们需要强调图书馆助手吗?
我发现这对谷歌很有用
【参考方案23】:
我认为@Benjamin 的window.onload
建议不会一直有效,因为它不会检测是否在加载后调用它。我已经被那个咬过很多次了。这是一个应该始终有效的版本:
function promiseDOMready()
return new Promise(function(resolve)
if (document.readyState === "complete") return resolve();
document.addEventListener("DOMContentLoaded", resolve);
);
promiseDOMready().then(initOnLoad);
【讨论】:
“已经完成”的分支不应该使用setTimeout(resolve, 0)
(或setImmediate
,如果可用)来确保它被异步调用吗?
@Alnitak 同步调用resolve
很好。无论是否同步调用resolve
,Promise 的then
处理程序都是guaranteed by the framework to be called asynchronously。以上是关于如何将现有的回调 API 转换为 Promise?的主要内容,如果未能解决你的问题,请参考以下文章