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

Posted

技术标签:

【中文标题】如何将现有的回调 API 转换为 Promise?【英文标题】:How do I convert an existing callback API to promises? 【发布时间】:2022-01-17 23:18:05 【问题描述】:

我想使用 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 有状态,它们以待处理状态开始并且可以解决:

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 的博文,供任何想了解更多有关该主题的人使用。【参考方案2】:

我认为@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。【参考方案3】:

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

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

可以用 Q.ninvoke 转换

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

【讨论】:

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

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

【讨论】:

【参考方案5】:

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

【讨论】:

【参考方案6】:

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

【讨论】:

【参考方案7】:

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

希望这会有所帮助。

【讨论】:

这些似乎没有显示如何转换为承诺。【参考方案8】:

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

【讨论】:

【参考方案9】:

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

【讨论】:

【参考方案10】:

在 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 会正确。【参考方案11】:

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

回调风格函数总是这样(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 的函数式编程库分支(由我创建)。

希望这个答案对你有用。

【讨论】:

【参考方案13】:

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

【讨论】:

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

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

const promisify = require('es6-promisify');

const promisedFn = promisify(callbackedFn, args);

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

【讨论】:

【参考方案16】:

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

var P = function() 
  var self = this;
  var method = arguments[0];
  var params = Array.prototype.slice.call(arguments, 1);
  return new Promise((resolve, reject) => 
    if (method && typeof(method) == 'function') 
      params.push(function(err, state) 
        if (!err) return resolve(state)
        else return reject(err);
      );
      method.apply(self, params);
     else return reject(new Error('not a function'));
  );

var callback = function(par, callback) 
  var rnd = Math.floor(Math.random() * 2) + 1;
  return rnd > 1 ? callback(null, par) : callback(new Error("trap"));


callback("callback", (err, state) => err ? console.error(err) : console.log(state))
callback("callback", (err, state) => err ? console.error(err) : console.log(state))
callback("callback", (err, state) => err ? console.error(err) : console.log(state))
callback("callback", (err, state) => err ? console.error(err) : console.log(state))

P(callback, "promise").then(v => console.log(v)).catch(e => console.error(e))
P(callback, "promise").then(v => console.log(v)).catch(e => console.error(e))
P(callback, "promise").then(v => console.log(v)).catch(e => console.error(e))
P(callback, "promise").then(v => console.log(v)).catch(e => console.error(e))

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

【讨论】:

与原生 promisify 或上述答案相比,这有什么优势? 原生 Promisify 是什么意思? util.promisify(fn) 啊,当然:)。只是和例子来展示基本的想法。事实上,您可以看到即使是原生的也要求函数签名必须像(err, value) => ... 那样定义,或者您必须定义一个自定义的签名(请参阅自定义承诺函数)。谢谢你的好消息。 @loretoparisi 仅供参考,var P = function (fn, ...args) return new Promise((resolve, reject) => fn.call(this, ...args, (error, result) => error ? reject(error) : resolve(result))); ; 会做和你一样的事情,而且要简单得多。【参考方案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】:

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

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

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

【讨论】:

这不是一个承诺,它不会链接,处理回调中抛出的错误或接受第二个参数...【参考方案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】:

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

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(() =&gt; alert("Done!"); );,则永远不会触发警报。你希望这能奏效吗? 谢谢@Philip Stratford 的提问。 promisify 用于将带有回调的函数转换为 Promise。我会更新我的答案来解释这一点。 我很高兴听到有关此解决方案的任何建议,抄送@Philip Stratford。谢谢【参考方案21】:

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

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

【讨论】:

【参考方案22】:

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

【讨论】:

【参考方案23】:

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

【讨论】:

【参考方案24】:

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

【讨论】:

以上是关于如何将现有的回调 API 转换为 Promise?的主要内容,如果未能解决你的问题,请参考以下文章

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

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

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

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

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

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