【问题标题】:How do I convert an existing callback API to promises?如何将现有的回调 API 转换为 Promise?
【发布时间】:2026-02-12 22:40:01
【问题描述】:

我想使用 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 的异步调用

标签: javascript node.js callback promise bluebird


【解决方案1】:

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 处理程序返回一个承诺将使用该承诺的值解决或拒绝。从.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 的博文,供任何想了解更多有关该主题的人使用。
【解决方案2】:

今天,我可以在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 用法的结果。

希望这会有所帮助。

【讨论】:

  • 这些似乎没有显示如何转换为承诺。
【解决方案3】:

在 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)
});

【讨论】:

    【解决方案4】:

    我认为@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
    【解决方案5】:

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

    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 util
      promise = promisify(sum, 3, 1, cb);
      promise.then(x => console.log(x)) // 4
    

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

    【讨论】:

    • 我正在尝试使用它,但如果我调用promisify(fn, arg1, arg2).then(() => { alert("Done!"); });,则永远不会触发警报。你希望这能奏效吗?
    • 谢谢@Philip Stratford 的提问。 promisify 用于将带有回调的函数转换为 Promise。我会更新我的答案来解释这一点。
    • 我很高兴听到有关此解决方案的任何建议,抄送@Philip Stratford。谢谢
    【解决方案6】:

    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 我对此表示赞同,因为它不那么“杂乱”且有效。最上面的那个有很多其他的东西,以至于答案丢失了。
    【解决方案7】:

    在 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 会正确。
    【解决方案8】:

    您可以在 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
    

    【讨论】:

      【解决方案9】:

      使用普通的老式 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);
      });
      

      【讨论】:

        【解决方案10】:

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

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

        可以用 Q.ninvoke 转换

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

        【讨论】:

        • 规范答案已经提到Q.denodeify。我们需要强调图书馆助手吗?
        • 我发现这对谷歌很有用
        【解决方案11】:

        当您有几个函数需要回调并且您希望它们返回一个 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;
            }
        }
        

        【讨论】:

          【解决方案12】:

          在 node v7.6+ 下,内置了 promises 和 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;
          }
          

          【讨论】:

            【解决方案13】:

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

            【讨论】:

              【解决方案14】:

              您可以在 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() 永远不会被调用。

              【讨论】:

                【解决方案15】:

                回调风格函数总是这样(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 是一个优秀的函数式编程库。在上面的代码中,我们使用了 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】:

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

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

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

                  【讨论】:

                    【解决方案17】:

                    你可以这样做

                    // @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(它会自动为您执行此操作)。还不清楚这适用于哪些功能(在成功时使用单个参数调用回调?如何处理错误?)
                    【解决方案18】:

                    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 是什么意思?
                    • 啊,当然:)。只是和例子来展示基本的想法。事实上,您可以看到即使是原生的也要求函数签名必须像(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))); }; 会做和你一样的事情,而且要简单得多。
                    【解决方案19】:

                    以下是如何将函数(回调 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');
                    
                    

                    【讨论】:

                      【解决方案20】:

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

                      // 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)
                        }
                      }
                      

                      【讨论】:

                        【解决方案21】:

                        通灵一点,这个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();

                        【讨论】:

                          【解决方案22】:

                          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 上寻找现代包装器,或者像这样编写自己的包装器。

                          【讨论】:

                            【解决方案23】:

                            由于我们事先知道基于回调的函数的特点, 我们可以创建一个函数来转换基于回调的函数 转换为返回 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 arguments
                                      yourCallbackApi(...newArgs);
                                    });
                                  };
                                }
                              

                            【讨论】:

                              【解决方案24】:

                              好像晚了 5 年,但我想在这里发布我的 promesify 版本,它从回调 API 中获取函数并将它们转换为 Promise

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

                              在这里查看这个非常简单的版本: https://gist.github.com/jdtorregrosas/aeee96dd07558a5d18db1ff02f31e21a

                              【讨论】:

                              • 这不是一个承诺,它不会链接,处理回调中抛出的错误或接受第二个参数...