我想使用promises,但我有一个回调API,格式如下:
window.onload; // set to callback
...
window.onload = function() {
};
function request(onChangeHandler) {
...
}
request(function() {
// change happened
...
});
function getStuff(dat, callback) {
...
}
getStuff("dataParam", function(err, data) {
...
})
API;
API.one(function(err, data) {
API.two(function(err, data2) {
API.three(function(err, data3) {
...
});
});
});
承诺有状态,他们开始等待,并可以解决:
承诺返回功能should never throw,他们应该返回拒绝。从承诺返回功能投掷会迫使你使用} catch {
和.catch
。使用promisified API的人不希望承诺投掷。如果您不确定异步API如何在JS中运行 - 请先see this answer。
因此,创建承诺通常意味着指定它们何时结算 - 这意味着当它们移动到已完成或被拒绝的阶段以指示数据可用时(并且可以使用.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;
}
或者使用类似API的jQuery,挂钩一次发生的事件:
function done() {
var d = $.Deferred();
$("#myObject").once("click",function() {
d.resolve();
});
return d.promise();
}
这些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();
}
注意:在这里我们利用jQuery deferred的resolve
和reject
方法是“可拆卸”的事实;即。它们绑定到jQuery.Deferred()的实例。并非所有的lib都提供此功能。
节点样式回调(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+中的本机承诺的库都有内置的方法来实现节点回发。例如
var getStuffAsync = Promise.promisify(getStuff); // Bluebird
var getStuffAsync = Q.denodeify(getStuff); // Q
var getStuffAsync = util.promisify(getStuff); // Native promises, node only
这里没有黄金法则,你一个接一个地宣传它们。但是,一些promise实现允许您批量执行此操作,例如在Bluebird中,将nodeback API转换为promise API非常简单:
Promise.promisifyAll(API);
或者使用Node中的本机承诺:
const { promisify } = require('util');
const promiseAPI = Object.entries(API).map(v => ({key, fn: promisify(v)}))
.reduce((o, p) => Object.assign(o, {[p.key]: p.fn}), {});
笔记:
.then
处理程序时,你不需要宣传事物。从.then
处理程序返回一个promise将解决或拒绝该promise的值。从.then
处理程序投掷也是很好的做法,并将拒绝承诺 - 这是着名的承诺投掷安全。onload
案例中,你应该使用addEventListener
而不是onX
。当你有一些函数需要回调并且你希望它们返回一个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;
}
}
使用普通的旧版香草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);
});
在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);
// ...
});
你甚至可以使用原生的call
和apply
绑定一些上下文:
doAsync(myFunc).apply(context, params)
.then(result => { /*...*/ });
您可以在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()
永远不会被调用。
回调样式函数总是这样(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是一个优秀的函数式编程库。在上面的代码中,我们使用它的应用(如javascript function.prototype.apply
)和追加(如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创建的函数式编程库(由我创建)。
希望这个答案对你有用。
你可以做这样的事情
// @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)
}
es6-promisify
将基于回调的函数转换为基于Promise的函数。
const promisify = require('es6-promisify');
const promisedFn = promisify(callbackedFn, args);
我的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)
。
我创建了一个可以重用的泛型函数。
const wrapIntoPromise: (fnToExecute) => {
return new Promise((resolve, reject) => {
return fnToExecute(resolve, reject);
});
}
//example
async function run() {
await wrapIntoPromise((resolve, reject) => {
/*execute any function here with callbacks etc
when done just call return resolve(valueToReturn)
*/
return backup({
callback: function() { return resolve()}
});
});
}
下面是如何将函数(回调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');
今天,我可以在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 )
})
}
(我建议访问this beautiful source)
Promise
也可以与async\await
中的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使用的结果。
希望这可以帮助。
这已经晚了5年,但是我想在这里发布我的promesify版本,它接受来自回调API的功能并将它们变成承诺
const promesify = fn => {
return (...params) => ({
then: cbThen => ({
catch: cbCatch => {
fn(...params, cbThen, cbCatch);
}
})
});
};
看看这个非常简单的版本:https://gist.github.com/jdtorregrosas/aeee96dd07558a5d18db1ff02f31e21a
在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)
});
我不认为@Benjamin的window.onload
建议会一直有效,因为它不会检测它是否在加载后被调用。我被这多次咬过了。这是一个应该始终有效的版本:
function promiseDOMready() {
return new Promise(function(resolve) {
if (document.readyState === "complete") return resolve();
document.addEventListener("DOMContentLoaded", resolve);
});
}
promiseDOMready().then(initOnLoad);
在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(...);
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) => { /** ... **/ });
您可以在Node JS中使用JavaScript本机承诺。
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
kriskowal的Q库包括回调承诺函数。像这样的方法:
obj.prototype.dosomething(params, cb) {
...blah blah...
cb(error, results);
}
可以用Q.ninvoke转换
Q.ninvoke(obj,"dosomething",params).
then(function(results) {
});
在节点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;
}