如何将现有的callbackAPI转换为承诺?
我想使用承诺,但我有一个格式的callbackAPI:
1. DOM负载或其他一次性事件:
window.onload; // set to callback ... window.onload = function(){ };
2.普通callback:
function request(onChangeHandler){ ... request(function(){ // change happened });
3.节点样式callback(“nodeback”):
function getStuff(dat,callback){ ... getStuff("dataParam",function(err,data){ }
4.具有节点样式callback的整个库:
API; API.one(function(err,data){ API.two(function(err,data2){ API.three(function(err,data3){ }) }); });
我如何在承诺中使用API,如何“promisify”呢?
承诺有国家,他们开始等待,可以解决:
- 意味着计算成功完成。
- 被拒绝的意思是计算失败。
承诺返回函数不应该抛出 ,他们应该返回拒绝。 从promise返回函数抛出将迫使你使用} catch {
和 .catch
。 使用promisified APIs的人不期望承诺会抛出。 如果您不确定asynchronousAPI如何在JS中工作,请首先看到这个答案 。
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; }
或者像API一样使用jQuery来挂钩一个事件:
function done(){ var d = $.Deferred(); $("#myObject").once("click",function(){ d.resolve(); }); return d.promise(); }
2.普通callback:
这些API是相当普遍的,因为…callback在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)
表单的expression式,如下所示:
function getUserDataAsync(userId) { return $.Deferred(function(dfrd) { getUserData(userId, dfrd.resolve, dfrd.reject); }).promise(); }
注意:这里我们利用jQuery延迟的resolve
和reject
方法是“可拆卸”的事实; 即。 它们绑定到jQuery.Deferred()的实例 。 并不是所有的库都提供这个function。
3.节点样式callback(“nodeback”):
节点样式callback(nodebacks)有一个特定的格式,其中callback总是最后一个参数,其第一个参数是一个错误。 我们先手动提示一个:
getStuff("dataParam",function(err,data){
至:
function getStuffAsync(param){ return new Promise(function(resolve,reject){ getStuff(param,function(err,data){ if(err !== null) return reject(err); resolve(data); }); }); }
有了deferred,你可以做下面的事情(让我们用Q代表这个例子,尽pipeQ现在支持你应该更喜欢的新语法):
function getStuffAsync(param){ var d = Q.defer(); getStuff(param,function(err,data){ if(err !== null) return d.reject(err); // `throw err` also works here. d.resolve(data); }); return d.promise; }
一般来说,你不应该手动地提供过多的东西,大多数承诺在Node中devise的库以及在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.具有节点样式callback的整个库:
这里没有黄金法则,你们一个接一个地给予他们。 但是,有些承诺实现允许您批量执行此操作,例如在Bluebird中,将nodeback API转换为承诺API非常简单:
Promise.promisifyAll(API);
或者在Node中使用本地承诺:
const promiseAPI = Object.keys(API).map(key => {key, fn: util.promisify(API[key])) .reduce((o, p) => Object.assign(o, {[p.key] : p.fn}), {});
笔记:
- 当然,当你在一个处理程序中时,你不需要提出任何事情。 然后从一个处理者那里得到一个承诺
.then
就可以解决或拒绝这个承诺的价值。 从一个处理者投掷也是一个很好的做法,并会拒绝承诺 – 这是着名的诺言安全。 - 在实际的
onload
情况下,你应该使用addEventListener
而不是onX
。
我不认为@Benjamin的window.onload
build议会一直工作,因为它不检测是否在加载后调用它。 我被这么多次咬了 这是一个应该始终工作的版本:
function promiseDOMready() { return new Promise(function(resolve) { if (document.readyState === "complete") return resolve(); document.addEventListener("DOMContentLoaded", resolve); }); } promiseDOMready().then(initOnLoad);
今天,我可以使用Node.js
中的Promise
作为一个简单的Javascript方法。
Promise
一个简单和基本的例子(用KISS方式):
纯Javascript JavascriptasynchronousAPI代码:
function divisionAPI (number, divider, successCallback, errorCallback) { if (divider == 0) { return errorCallback( new Error("Division by zero") ) } successCallback( number / divider ) }
Promise
JavascriptasynchronousAPI代码:
function divisionAPI (number, divider) { return new Promise(function (fullfilled, rejected) { if (divider == 0) { return rejected( new Error("Division by zero") ) } fullfilled( number / divider ) }) }
(我build议访问这个美丽的来源 )
另外, Promise
可以与ES7
async\await
一起使用,以使程序stream等待全部结果的结果,如下所示:
function getName () { return new Promise(function (fullfilled, rejected) { var name = "John Doe"; // wait 3000 milliseconds before calling fulfilled() method setTimeout ( function() { fullfilled( name ) }, 3000 ) }) } async function foo () { var name = await getName(); // awaits for a fullfilled 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 (fullfilled, rejected) { var name = "John Doe"; // wait 3000 milliseconds before calling fulfilled() method setTimeout ( function() { fullfilled( name ) }, 3000 ) }) } // the console writes "John Doe" after 3000 milliseconds getName().then(function(name){ console.log(name) })
希望这可以帮助。
在Node.js 8.0.0的候选版本中,有一个新的工具util.promisify
(我已经写了关于util.promisify ),它封装了promization任何函数的能力。
与其他答案中提出的方法没有什么不同,但是具有作为核心方法的优点,并且不需要额外的依赖性。
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(...);
在将函数转换为promise之前在Node.JS中
var request = require('request'); //http wrapped module function reqeustWrapper(url, callback) { request.get(url, function (err, response) { if (err) { callback(err); }else{ callback(response); } }) } requestWrapper(url,function(response){ console.log(response) })
转换后
var request = require('request'); var Promise = require('bluebird'); function reqeustWrapper(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) });
您可以使用JavaScript原生承诺与节点JS。
我的云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库包括callback函数。 像这样的一个方法:
obj.prototype.dosomething(params, cb) { ...blah blah... cb(error, results); }
可以用Q.ninvoke转换
Q.ninvoke(obj,"dosomething",params). then(function(results) { });
当你有几个函数需要callback,而你希望他们返回一个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,这是一个promisify apicallback的解决scheme。
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); });
在build立在承诺和asynchronous的节点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; }
您可以在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 8.0.0包含一个新的util.promisify()
API,它允许将标准的Node.jscallback风格的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) => { /** ... **/ });
请参阅改进对Promise的支持
callback样式函数总是这样的(几乎node.js中的所有函数都是这种样式):
//fs.readdir(path[, options], callback) fs.readdir('mypath',(err,files)=>console.log(files))
这种风格有相同的特点:
-
callback函数由最后一个parameter passing。
-
callback函数总是接受错误对象作为第一个参数。
所以,你可以写一个函数来转换一个函数,像这样:
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是function性编程的优秀库。 在上面的代码中,我们使用了它(如javascript function.prototype.apply
)并追加(如javascript function.prototype.push
)。 所以,我们现在可以将一个callback样式函数转换为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 (由我创build)的函数式编程库fork。
希望这个答案对你有用。
在Node.js 8中,你可以使用这个npm模块dynamic调用对象方法:
https://www.npmjs.com/package/doasync
它使用util.promisify和代理,使您的对象保持不变。 Memoization也使用WeakMaps来完成)。 这里有些例子:
与对象:
const fs = require('fs'); const doAsync = require('doasync'); doAsync(fs).readFile('package.json', 'utf8') .then(result => { console.dir(JSON.parse(result), {colors: true}); });
具有function:
doAsync(request)('http://www.google.com') .then(({body}) => { console.log(body); // ... });
你甚至可以使用本地call
并apply
绑定一些上下文:
doAsync(myFunc).apply(context, params) .then(result => { /*...*/ });
es6-promisify
将基于callback的函数转换为基于Promise的函数。
const promisify = require('es6-promisify'); const promisedFn = promisify(callbackedFn, args);
参考: https : //www.npmjs.com/package/es6-promisify
您可以使用callback2Promise npm包将节点样式函数转换为Promises.`
var c2p = require('callback2promise'); // ordinary function with any number of parameters and a callback at the end var nodeStyleFunc = function(param1, param2, callback){ setTimeout( function(){ callback(null, 'done') }, 200); } // convert the function to a promise var promise = c2p(nodeStyleFunc)(param1, param2); promise .then(result => console.log(result)) .catch(err => console.log(err));