如何将现有的回调 API 转换为承诺?
- 2024-11-02 21:00:00
- admin 原创
- 31
问题描述:
我想使用承诺,但我有一个回调 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) {
...
});
});
});
我如何使用承诺中的 API,如何“承诺化”它们?
解决方案 1:
承诺是有状态的,它们以待决状态开始,并可以稳定为:
done表示计算已成功完成。
拒绝意味着计算失败。
返回 Promise 的函数永远不应该抛出,而应该返回拒绝。从返回 Promise 的函数抛出异常将迫使您同时使用 a} catch {
和a .catch
。使用 Promised API 的人并不希望 Promise 抛出异常。如果您不确定 JS 中的异步 API 是如何工作的 - 请先查看此答案。
1. DOM 加载或其他一次性事件:
因此,创建承诺通常意味着指定它们何时解决 - 这意味着当它们进入履行或拒绝阶段以表明数据可用(并且可以通过 访问.then
)时。
通过支持Promise
像原生 ES6 承诺那样的构造函数的现代承诺实现:
function load() {
return new Promise(function(resolve, reject) {
window.onload = resolve;
});
}
然后你可以像这样使用得到的承诺:
load().then(function() {
// Do things after onload
});
使用支持延迟的库(我们在这里使用 $q 作为示例,但稍后我们也将使用 jQuery):
function load() {
var d = $q.defer();
window.onload = function() { d.resolve(); };
return d.promise;
}
或者使用类似 jQuery 的 API,挂接一次发生的事件:
function done() {
var d = $.Deferred();
$("#myObject").once("click",function() {
d.resolve();
});
return d.promise();
}
2. 普通回调:
这些 API 相当常见,因为回调在 JS 中很常见。让我们看看常见的情况onSuccess
:onFail
function getUserData(userId, onLoad, onFail) { …
通过支持Promise
像原生 ES6 承诺那样的构造函数的现代承诺实现:
function getUserDataAsync(userId) {
return new Promise(function(resolve, reject) {
getUserData(userId, resolve, reject);
});
}
使用支持延迟的库(我们在这里使用 jQuery 作为示例,但我们上面也使用了 $q):
function getUserDataAsync(userId) {
var d = $.Deferred();
getUserData(userId, function(res){ d.resolve(res); }, function(err){ d.reject(err); });
return d.promise();
}
jQuery 还提供了一种$.Deferred(fn)
形式,其优点是允许我们编写一个非常接近模拟该new Promise(fn)
形式的表达式,如下所示:
function getUserDataAsync(userId) {
return $.Deferred(function(dfrd) {
getUserData(userId, dfrd.resolve, dfrd.reject);
}).promise();
}
resolve
注意:这里我们利用了 jQuery 延迟和方法是“可分离”的事实reject
;即它们绑定到jQuery.Deferred() 的实例。并非所有库都提供此功能。
3. 节点样式回调(“nodeback”):
节点样式回调 (nodebacks) 具有特定格式,其中回调始终是最后一个参数,其第一个参数是错误。让我们首先手动 promisify 一个:
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);
});
});
}
使用 deferreds 你可以做以下事情(我们使用 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 都内置了用于承诺节点返回的方法。例如
var getStuffAsync = Promise.promisify(getStuff); // Bluebird
var getStuffAsync = Q.denodeify(getStuff); // Q
var getStuffAsync = util.promisify(getStuff); // Native promises, node only
4. 具有节点样式回调的完整库:
这里没有黄金法则,你可以逐个地将它们承诺化。但是,一些承诺实现允许你批量执行此操作,例如在 Bluebird 中,将 nodeback API 转换为承诺 API 非常简单:
Promise.promisifyAll(API);
或者使用Node中的原生承诺:
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
。
解决方案 2:
今天,我可以将Promise
其用作Node.js
一个简单的 Javascript 方法。
一个简单而基本的例子Promise
(用KISS方式):
普通Javascript 异步 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 )
})
}
(我建议参观这个美丽的源头)
也可以与inPromise
一起使用,使程序流等待结果,如下所示:asyncawait
`ES7`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
。
奖励:混合方法
(回调方法假定有两个参数,即错误和结果)
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:
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) => { /* ... */ });
请参阅对 Promises 的改进支持
解决方案 5:
我认为window.onload
@Benjamin 的建议并不总是有效,因为它无法检测是否在加载后调用。我被这个问题困扰过很多次。这是一个应该始终有效的版本:
function promiseDOMready() {
return new Promise(function(resolve) {
if (document.readyState === "complete") return resolve();
document.addEventListener("DOMContentLoaded", resolve);
});
}
promiseDOMready().then(initOnLoad);
解决方案 6:
我通常使用的简单通用函数。
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
你可能不需要这个答案,但这将有助于理解可用实用程序的内部工作原理
解决方案 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(...);
解决方案 8:
您可以将 JavaScript 原生承诺与 Node JS 结合使用。
我的 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) {
});
解决方案 11:
当您有几个接受回调的函数并且希望它们返回一个承诺时,您可以使用此函数进行转换。
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:
在已内置 Promise 和 Async 的 Node 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;
}
解决方案 13:
在 Node.js 8 中,你可以使用这个 npm 模块动态地承诺对象方法:
https://www.npmjs.com/package/doasync
它使用util.promisify和Proxies来使对象保持不变。记忆化也是通过使用 WeakMaps 来实现的。以下是一些示例:
使用对象:
const fs = require('fs');
const doAsync = require('doasync');
doAsync(fs).readFile('package.json', 'utf8')
.then(result => {
console.dir(JSON.parse(result), {colors: true});
});
具有以下功能:
doAsync(request)('http://www.google.com')
.then(({body}) => {
console.log(body);
// ...
});
您甚至可以使用 nativecall
和apply
来绑定一些上下文:
doAsync(myFunc).apply(context, params)
.then(result => { /*...*/ });
解决方案 14:
你可以在 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
});
}
在这个例子中,Promise 没有失败的理由,所以reject()
永远不会被调用。
解决方案 15:
es6-promisify
将基于回调的函数转换为基于 Promise 的函数。
const promisify = require('es6-promisify');
const promisedFn = promisify(callbackedFn, args);
参考:https: //www.npmjs.com/package/es6-promisify
解决方案 16:
你可以做这样的事情
// @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)
}
解决方案 17:
回调风格函数总是这样的(node.js中几乎所有函数都是这种风格):
//fs.readdir(path[, options], callback)
fs.readdir('mypath',(err,files)=>console.log(files))
此风格有以下相同特点:
回调函数通过最后一个参数传递。
回调函数总是接受错误对象作为其第一个参数。
因此,您可以编写一个函数来转换具有这种风格的函数,如下所示:
const R =require('ramda')
/**
* A convenient function for handle error in callback function.
* Accept two function res(resolve) and rej(reject) ,
* return a wrap function that accept a list arguments,
* the first argument as error, if error is null,
* the res function will call,else the rej function.
* @param {function} res the function which will call when no error throw
* @param {function} rej the function which will call when error occur
* @return {function} return a function that accept a list arguments,
* the first argument as error, if error is null, the res function
* will call,else the rej function
**/
const checkErr = (res, rej) => (err, ...data) => R.ifElse(
R.propEq('err', null),
R.compose(
res,
R.prop('data')
),
R.compose(
rej,
R.prop('err')
)
)({err, data})
/**
* wrap the callback style function to Promise style function,
* the callback style function must restrict by convention:
* 1. the function must put the callback function where the last of arguments,
* such as (arg1,arg2,arg3,arg...,callback)
* 2. the callback function must call as callback(err,arg1,arg2,arg...)
* @param {function} fun the callback style function to transform
* @return {function} return the new function that will return a Promise,
* while the origin function throw a error, the Promise will be Promise.reject(error),
* while the origin function work fine, the Promise will be Promise.resolve(args: array),
* the args is which callback function accept
* */
const toPromise = (fun) => (...args) => new Promise(
(res, rej) => R.apply(
fun,
R.append(
checkErr(res, rej),
args
)
)
)
为了更简洁,上面的例子使用了 ramda.js。Ramda.js 是一个优秀的函数式编程库。在上面的代码中,我们使用了它的apply(像 javascript function.prototype.apply
)和 append(像 javascript function.prototype.push
)。因此,我们现在可以将回调样式函数转换为 promise 样式函数:
const {readdir} = require('fs')
const readdirP = toPromise(readdir)
readdir(Path)
.then(
(files) => console.log(files),
(err) => console.log(err)
)
toPromise和checkErr函数是berserk库所拥有的,它是由ramda.js (由我创建) fork 的一个函数式编程库。
希望这个答案对你有用。
解决方案 18:
我的 promisify 版本的callback
函数是该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))
运行代码片段Hide results展开片段
该P
函数要求回调签名必须是callback(error,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:
Promises 总是有一个resolve
和一个reject
。当你编写异步包装器时,只需调用 resolve 就可以了。
您可以为几乎任何接受回调的函数编写一个包装函数,如下所示:
const myAsyncWrapper = (...params) =>
new Promise((resolve, reject) =>
someFunctionWithCallback(...params, (error, response) =>
error ? reject(error) : resolve(response)
)
);
您可以进一步编写一个将回调转换为承诺的函数:
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
});
在现代应用程序中,使用基于承诺的 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)))
);
尽管 Promisifier 有时很棒,但它们不适用于这种特定情况。在这种情况下,请在 Github 上寻找现代包装器,或者像这样编写自己的包装器。
解决方案 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:
有点死灵法术,但这个链接可能会有用......
TLDR;查看本答案末尾的代码片段示例
编写/转换可以调用的函数
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();
运行代码片段Hide results展开片段
解决方案 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 中获取函数并将它们转换为承诺
const promesify = fn => {
return (...params) => ({
then: cbThen => ({
catch: cbCatch => {
fn(...params, cbThen, cbCatch);
}
})
});
};
请看这里非常简单的版本:
https://gist.github.com/jdtorregrosas/aeee96dd07558a5d18db1ff02f31e21a
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件