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

How do I convert an existing callback API to promises?

提问人:Benjamin Gruenbaum 提问时间:3/20/2014 最后编辑:onmyway133Benjamin Gruenbaum 更新时间:3/26/2022 访问量:320813

问:

我想使用 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,如何“承诺”它?

JavaScript 节点.js 回调 承诺 蓝鸟

评论

0赞 Benjamin Gruenbaum 3/20/2014
我发布了我自己的答案,但非常欢迎关于如何为特定图书馆或在更多情况下执行此操作的答案和编辑。
0赞 Benjamin Gruenbaum 3/20/2014
@Bergi 这是一个有趣的想法,我试图使用两种常见的方法(Promise 构造函数和延迟对象)来给出一个通用的答案。我试图在答案中给出两种选择。我同意 RTFMing 解决了这个问题,但我们经常在这里和错误跟踪器中遇到这个问题,所以我认为一个“规范问题”已经到位 - 我认为 RTFMing 解决了 JS 标签中大约 50% 的问题:D如果您有有趣的见解可以在答案或编辑中做出贡献,我们将不胜感激。
0赞 Ronnie Royston 1/22/2019
创建是否会增加任何重大开销?我想将所有同步 Noje.js 函数包装在一个 Promise 中,以便从我的 Node 应用程序中删除所有同步代码,但这是最佳实践吗?换句话说,一个接受静态参数(例如字符串)并返回计算结果的函数,我应该将其包装在 promise 中吗?...我在某处读到你不应该在 Nodejs 中有任何同步代码。new Promise
1赞 Benjamin Gruenbaum 1/22/2019
@RonRoyston不可以,用 promise 包装同步调用不是一个好主意 - 只有可能执行 I/O 的异步调用

答:

860赞 Benjamin Gruenbaum 3/20/2014 #1

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

  • 已实现表示计算成功完成。
  • rejected 表示计算失败。

promise 返回函数不应抛出,它们应该返回拒绝。从 promise 返回函数抛出将强制您同时使用 a a 。使用有前途的 API 的人并不期望承诺会抛出。如果您不确定异步 API 在 JS 中是如何工作的 - 请先查看此答案} catch {.catch

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

因此,创建承诺通常意味着指定它们何时结算 - 这意味着当它们进入已履行或拒绝阶段时,以指示数据可用(并且可以通过 访问)。.then

使用支持构造函数的现代 promise 实现,例如原生 ES6 promise:Promise

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

然后,您将使用生成的 promise,如下所示:

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

对于支持延迟的库(让我们在此示例中使用 $q,但我们稍后还将使用 jQuery):

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

或者使用类似 API 的 jQuery,挂钩一次发生的事件:

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

2. 普通回调:

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

function getUserData(userId, onLoad, onFail) { …

使用支持构造函数的现代 promise 实现,例如原生 ES6 promise:Promise

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

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

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

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

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

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

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 现在支持您应该首选的新语法):

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 的 promise 库以及 Node 8+ 中的原生 promise 都有一个内置的 promisization nodeback 方法。例如

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

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

这里没有黄金法则,你一个接一个地承诺。但是,一些 promise 实现允许您批量执行此操作,例如在 Bluebird 中,将节点回 API 转换为 promise API 非常简单:

Promise.promisifyAll(API);

或者在 Node 中使用原生 promise

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}), {});

笔记:

  • 当然,当你在处理程序中时,你不需要承诺事情。从处理程序返回 promise 将使用该 promise 的值进行解析或拒绝。从处理程序投掷也是一种很好的做法,并且会拒绝承诺 - 这就是著名的承诺投掷安全。.then.then.then
  • 在实际情况下,您应该使用 而不是 .onloadaddEventListeneronX

评论

0赞 Roamer-1888 4/19/2014
Benjamin,我接受了您的编辑邀请,并在案例 2 中添加了另一个 jQuery 示例。在出现之前,它需要同行评审。希望你喜欢它。
0赞 Benjamin Gruenbaum 4/24/2014
@Roamer-1888 年,它被拒绝了,因为我没有及时看到并接受它。就其价值而言,我不认为添加的内容太相关,尽管有用。
2赞 Roamer-1888 4/25/2014
Benjamin,无论是否被写成可重用的,我冒昧地认为我建议的编辑是相关的,因为它提供了一个 jQuery 表单示例,否则就缺乏了。如果只包含一个jQuery示例,那么我建议它应该采用这种形式,而不是等等,因为应该鼓励人们使用经常被忽视的形式,另外,在这样的答案中,它使jQuery与使用揭示构造函数模式的库相提并论。resolve()reject()$.Deferred(fn)var d = $.Deferred();$.Deferred(fn)
0赞 Benjamin Gruenbaum 4/25/2014
呵呵,100% 公平,我不知道 jQuery 让你这样做,如果你在接下来的 15 分钟内编辑它而不是现有的示例,我相信我可以尝试按时批准它:)$.Deferred(fn)
7赞 Bruno 5/16/2017
这是一个很好的答案。您可能还想通过提及 来更新它,Node.js 将从 RC 8.0.0 开始添加到其核心中。它的工作原理与 Bluebird 没有太大区别,但具有不需要额外依赖项的优点,以防您只想要原生 Promise。我写了一篇关于util.promisify的博客文章,供任何想要阅读更多关于该主题的人使用。util.promisifyPromise.promisify
25赞 Leo 1/14/2015 #2

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

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

评论

1赞 Alnitak 10/15/2015
“已经完成”的分支不应该使用(或,如果可用)来确保它是异步调用的吗?setTimeout(resolve, 0)setImmediate
6赞 Jeff Bowman 8/15/2016
@Alnitak同步调用就可以了。框架保证 Promise 的处理程序可以异步调用,而不管是否同步调用。resolvethenresolve
7赞 Jason Loveman 4/8/2015 #3

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

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

可以使用 Q.ninvoke 进行转换

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

评论

1赞 Bergi 4/8/2015
规范的答案已经提到.我们需要强调图书馆助手吗?Q.denodeify
3赞 Ed Sykes 12/28/2015
我发现这作为谷歌对 Q 潜在客户的承诺很有用
8赞 Apoorv 6/20/2016 #4

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

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赞 user1852503 8/4/2016 #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;
    }
}
8赞 daviddavis 11/28/2016 #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);
});
66赞 efkan 1/2/2017 #7

今天,我可以将 in 用作普通的 Javascript 方法。PromiseNode.js

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

平原Javascript 异步 API 代码:

function divisionAPI (number, divider, successCallback, errorCallback) {

    if (divider == 0) {
        return errorCallback( new Error("Division by zero") )
    }

    successCallback( number / divider )

}

承诺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 )

     })

}

(我推荐访问这个美丽的来源)

也可以与 in 一起使用,使程序流等待如下所示的结果:Promiseasync\awaitES7fullfiled

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赞 Dmitri Zaitsev 4/18/2018
这些似乎没有说明如何转换为承诺。
2赞 Nicolas Zozol 1/22/2017 #8

您可以在 ES6 中使用原生 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
    });

}

在这个例子中,应许没有理由失败,所以永远不会被召唤。reject()

5赞 Paul Spaulding 4/13/2017 #9

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

// 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;
}
15赞 Bruno 5/16/2017 #10

在 Node.js 8.0.0 的候选版本中,有一个新的实用程序(我已经写过关于 util.promisify),它封装了承诺任何函数的能力。util.promisify

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

const fs = require('fs');
const util = require('util');

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

然后,您有一个返回本机 .readFilePromise

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

评论

1赞 Benjamin Gruenbaum 5/16/2017
嘿,我(OP)实际上建议了两次(早在 2014 年写这个问题时,以及几个月前 - 我作为 Node 的核心成员推动了这个问题,这是我们在 Node 中的当前版本)。由于它尚未公开 - 我还没有将其添加到此答案中。不过,我们非常感谢使用反馈,并了解一些陷阱是什么,以便为发布:)提供更好的文档util.promisify
1赞 Benjamin Gruenbaum 5/16/2017
此外,您可能希望在博客文章中讨论用于承诺的自定义标志:)util.promisify
0赞 Bruno 5/17/2017
@BenjaminGruenbaum 你的意思是使用符号可以覆盖util.promisify的结果吗?老实说,这是一个故意的失误,因为我还没有找到一个有用的用例。也许你能给我一些意见?util.promisify.custom
1赞 Benjamin Gruenbaum 5/17/2017
当然,考虑不遵循 Node 约定的 API 或 API - 蓝鸟会弄错它们,但会弄对它们。fs.existsPromise.promisify util.promisify
25赞 Gian Marco 5/31/2017 #11

Node.js 8.0.0 包含一个新的 API,它允许将标准的 Node.js 回调样式 API 包装在返回 Promise 的函数中。的用法示例如下所示。util.promisify()util.promisify()

const fs = require('fs');
const util = require('util');

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

readFile('/some/file')
  .then((data) => { /* ... */ })
  .catch((err) => { /* ... */ });

请参阅改进了对 Promise 的支持

评论

2赞 Benjamin Gruenbaum 5/31/2017
已经有两个答案描述了这一点,为什么要发布第三个答案呢?
2赞 Gian Marco 6/15/2017
只是因为该版本的节点现已发布,并且我已经报告了“官方”功能描述和链接。
1赞 Lucio Mollinedo 8/6/2021
@BenjaminGruenbaum我对此投了赞成票,因为它不那么“杂乱”且有效。顶部的那个还有很多其他东西,以至于答案丢失了。
2赞 jituanlin 7/30/2017 #12

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

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

这种风格具有相同的功能:

  1. 回调函数由 Last 参数传递。

  2. 回调函数始终接受 Error 对象作为其第一个参数。

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

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 )和 append(如 javascript )。 因此,我们现在可以将 a 回调样式函数转换为 promise 样式函数:function.prototype.applyfunction.prototype.push

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

toPromisecheckErr 函数是狂暴库拥有的,它是 ramda.js(由我创建)的函数式编程库分支。

希望这个答案对你有用。

41赞 Siva Kannan 8/11/2017 #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)
});
4赞 Do Async 10/13/2017 #14

在 Node.js 8 中,您可以使用以下 npm 模块动态添加对象方法:

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

它使用 util.promisifyProxies,以便您的对象保持不变。记忆也是通过使用 WeakMap 完成的)。以下是一些示例:

使用对象:

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

您甚至可以使用 native 和 来绑定一些上下文:callapply

doAsync(myFunc).apply(context, params)
  .then(result => { /*...*/ });
2赞 Pujan 10/18/2017 #15

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

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

const promisedFn = promisify(callbackedFn, args);

编号: https://www.npmjs.com/package/es6-promisify

1赞 loretoparisi 11/30/2017 #16

我承诺的函数版本是函数:callbackP

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

该函数要求回调签名必须为 。Pcallback(error,result)

评论

1赞 Benjamin Gruenbaum 11/30/2017
与原生承诺或上述答案相比,这有什么优势?
0赞 loretoparisi 11/30/2017
你对原生承诺是什么意思?
0赞 Benjamin Gruenbaum 11/30/2017
util.promisify(fn)
0赞 loretoparisi 11/30/2017
啊,是的,当然:)。只是举例说明基本思想。事实上,你可以看到,即使是原生的,也要求函数签名必须定义一些,或者你必须定义一个自定义的(参见自定义承诺函数)。谢谢你,好catcha。(err, value) => ...
1赞 Patrick Roberts 5/11/2019
@loretoparisi仅供参考,会做和你一样的事情,而且要简单得多。var P = function (fn, ...args) { return new Promise((resolve, reject) => fn.call(this, ...args, (error, result) => error ? reject(error) : resolve(result))); };
2赞 onmyway133 10/9/2018 #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)
}

评论

2赞 Benjamin Gruenbaum 10/9/2018
嘿,我不确定这对现有答案有什么影响(也许澄清一下?此外,不需要在 promise 构造函数中使用 try/catch(它会自动为您执行此操作)。目前还不清楚这适用于哪些函数(在成功时使用单个参数调用回调?如何处理错误?
-3赞 Julian Torregrosa 4/5/2019 #18

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

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

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

评论

2赞 Benjamin Gruenbaum 4/5/2019
这不是一个承诺,它不会链接,处理回调中抛出的错误或接受第二个参数......
1赞 Mzndako 6/21/2019 #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');

26赞 Josiah Nyarega 9/25/2020 #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

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

评论

0赞 Philip Stratford 10/4/2021
我正在尝试使用它,但是如果我调用警报,则永远不会触发警报。你期望这能奏效吗?promisify(fn, arg1, arg2).then(() => { alert("Done!"); });
1赞 Josiah Nyarega 10/16/2021
谢谢,斯特拉特福@Philip的问题。用于将带有回调的函数转换为 promise。我将更新我的答案来解释这一点。promisify
0赞 Josiah Nyarega 10/16/2021
我很高兴听到有关此解决方案的任何建议,抄送@Philip斯特拉特福。谢谢
0赞 Amarkant Kumar 8/4/2023
const promisify = (fn, ...args) => { return new Promise((resolve, reject) => { fn.apply(null, args.concat((err, result) => { if (err) reject(err); else resolve(result); })); });const cb = (结果) => 结果;const sum = (a, b, cb, t) => { t('rr', cb(a + b)) } promise = promisify(sum, 3, 1, cb);promise.then(x => console.log(x)).catch((err) => console.log(err)) // 4
0赞 smallscript 12/27/2020 #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)
  }
}
0赞 unsynchronized 6/15/2021 #22

死灵一点,这个链接可能有用......


顶级域名;请看这个答案末尾的片段示例


可以调用预期的写入/转换函数

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


  • promiseToCB转换并导出先前编码为返回 promise 的现有函数
  • cbToPromise转换并导出先前编码为使用 (error,result) 调用最后一个参数的现有函数
    • 如果 wrapped 函数提供 1 个以上的结果,则结果将是一个结果数组
    • 例如cb(undefined,path,stat) ---> resolve([path,stat]) / cb(undefined,[path,stat])
  • asPromise允许您编写新函数以返回 promise,但可以以任何一种方式调用它
  • 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);
        
    });}

测试上述函数的 scipt


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

1赞 Jacob 12/31/2021 #23

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

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

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

0赞 Yilmaz 1/31/2022 #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);
          });
        };
      }