如何将现有的回调 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 中很常见。让我们看一下拥有onSuccessonFail 的常见情况:

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 的 resolvereject 方法是“可分离的”这一事实; 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)

还可以将PromiseES7 中的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) =&gt; ... 那样定义,或者您必须定义一个自定义的签名(请参阅自定义承诺函数)。谢谢你的好消息。 @loretoparisi 仅供参考,var P = function (fn, ...args) return new Promise((resolve, reject) =&gt; fn.call(this, ...args, (error, result) =&gt; 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.promisifyProxies 使您的对象保持不变。 Memoization 也是使用 Wea​​kMaps 完成的)。以下是一些示例:

有对象:

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 =>  /*...*/ );

【讨论】:

【参考方案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)
    )

toPromisecheckErr 函数由 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?的主要内容,如果未能解决你的问题,请参考以下文章

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

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

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

如何在node js中将promise转换为回调?

如何将现有的非文档核心数据存储转换为 uimanageddocument?

如何将现有的 AngularJS 2 Web 应用程序转换为 Cordova 应用程序?