javascript - funciones - promesas typescript
¿Cómo convierto una API de devolución de llamada existente a promesas? (17)
Bajo el nodo v7.6 + que ha incorporado promesas y asíncronos:
// 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;
Cómo utilizar:
let readdir = require(''fs'').readdir;
let promisify = require(''./promisify'');
let readdirP = promisify(readdir);
async function myAsyncFn(path) {
let entries = await readdirP(path);
return entries;
}
Quiero trabajar con promesas pero tengo una API de devolución de llamada en un formato como:
1. DOM carga u otro evento único:
window.onload; // set to callback
...
window.onload = function() {
};
2. Devolución de llamada simple:
function request(onChangeHandler) {
...
}
request(function() {
// change happened
...
});
3. Devolución de llamada de estilo de nodo ("nodeback"):
function getStuff(dat, callback) {
...
}
getStuff("dataParam", function(err, data) {
...
})
4. Una biblioteca completa con devoluciones de llamada de estilo de nodo:
API;
API.one(function(err, data) {
API.two(function(err, data2) {
API.three(function(err, data3) {
...
});
});
});
¿Cómo trabajo con la API en promesas, cómo lo prometo?
Con javaScript de vainilla antiguo, aquí hay una solución para promisificar una devolución de llamada de 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);
});
Cuando tiene algunas funciones que aceptan una devolución de llamada y desea que devuelvan una promesa, puede utilizar esta función para realizar la conversión.
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;
}
}
En Node.js 8 puede promisificar métodos de objetos sobre la marcha usando este módulo npm:
https://www.npmjs.com/package/doasync
Utiliza util.promisify y Proxies para que sus objetos se mantengan sin cambios. La memorización también se realiza con el uso de WeakMaps). Aquí hay unos ejemplos:
Con objetos:
const fs = require(''fs'');
const doAsync = require(''doasync'');
doAsync(fs).readFile(''package.json'', ''utf8'')
.then(result => {
console.dir(JSON.parse(result), {colors: true});
});
Con funciones:
doAsync(request)(''http://www.google.com'')
.then(({body}) => {
console.log(body);
// ...
});
Incluso puedes usar la call
nativa y apply
para vincular algún contexto:
doAsync(myFunc).apply(context, params)
.then(result => { /*...*/ });
En la versión candidata para Node.js 8.0.0, hay una nueva utilidad, util.promisify
(he escrito sobre util.promisify ), que encapsula la capacidad de promisificar cualquier función.
No es muy diferente de los enfoques sugeridos en las otras respuestas, pero tiene la ventaja de ser un método central y no requerir dependencias adicionales.
const fs = require(''fs'');
const util = require(''util'');
const readFile = util.promisify(fs.readFile);
Entonces tienes un método readFile
que devuelve una Promise
nativa.
readFile(''./notes.txt'')
.then(txt => console.log(txt))
.catch(...);
Hoy, puedo usar Promise
in Node.js
como un método simple de Javascript.
Un ejemplo simple y básico de Promise
(con KISS way):
Código de API asíncrono de Javascript simple :
function divisionAPI (number, divider, successCallback, errorCallback) {
if (divider == 0) {
return errorCallback( new Error("Division by zero") )
}
successCallback( number / divider )
}
Código de API de Async de la Promise
Javascript:
function divisionAPI (number, divider) {
return new Promise(function (fulfilled, rejected) {
if (divider == 0) {
return rejected( new Error("Division by zero") )
}
fulfilled( number / divider )
})
}
(Recomiendo visitar esta hermosa fuente )
Además, Promise
se puede usar con async/await
ES7
en ES7
para hacer que el programa espere un resultado fullfiled
como el siguiente:
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
Otro uso con el mismo código usando el método .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
también se puede utilizar en cualquier plataforma basada en Node.js como react-native
.
Espero que esto ayude.
La biblioteca Q de kriskowal incluye funciones de devolución de llamada a promesa. Un método como este:
obj.prototype.dosomething(params, cb) {
...blah blah...
cb(error, results);
}
se puede convertir con Q.ninvoke
Q.ninvoke(obj,"dosomething",params).
then(function(results) {
});
La función de estilo de devolución de llamada siempre es así (casi toda la función en node.js es este estilo):
//fs.readdir(path[, options], callback)
fs.readdir(''mypath'',(err,files)=>console.log(files))
Este estilo tiene la misma característica:
La función de devolución de llamada se pasa por el último argumento.
La función de devolución de llamada siempre acepta el objeto de error como primer argumento.
Entonces, podrías escribir una función para convertir una función con este estilo como este:
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
)
)
)
Para más conciso, el ejemplo anterior utiliza ramda.js. Ramda.js es una excelente biblioteca para la programación funcional. En el código anterior, usamos su aplicación (como javascript function.prototype.apply
) y anexar (como javascript function.prototype.push
). Entonces, podríamos convertir la función de estilo de devolución de llamada para prometer ahora la función de estilo:
const {readdir} = require(''fs'')
const readdirP = toPromise(readdir)
readdir(Path)
.then(
(files) => console.log(files),
(err) => console.log(err)
)
La función toPromise y checkErr es propia de la biblioteca berserk , es una horquilla de la biblioteca de programación funcional por ramda.js (creada por mí).
Espero que esta respuesta te sea útil.
Las promesas tienen estado, comienzan como pendientes y pueden conformarse con:
- Cumplido lo que significa que el cálculo completado con éxito.
- rechazado, lo que significa que el cálculo falló.
Las funciones de devolución de promesas nunca deben lanzarse , sino que deben devolver rechazos. Lanzar desde una función de devolución de promesa lo forzará a usar tanto } catch {
como a .catch
. Las personas que utilizan API prometidas no esperan que las promesas se cumplan. Si no está seguro de cómo funcionan las API asíncronas en JS, consulte esta respuesta primero.
1. DOM carga u otro evento único:
Por lo tanto, crear promesas generalmente significa especificar cuándo se liquidan, es decir, cuando pasan a la fase cumplida o rechazada para indicar que los datos están disponibles (y se puede acceder a ellos con .then
).
Con las implementaciones de promesa modernas que son compatibles con el constructor Promise
como promesas nativas ES6:
function load() {
return new Promise(function(resolve, reject) {
window.onload = resolve;
});
}
Entonces usarías la promesa resultante así:
load().then(function() {
// Do things after onload
});
Con bibliotecas que admiten diferido (Vamos a usar $ q para este ejemplo aquí, pero también usaremos jQuery más adelante):
function load() {
var d = $q.defer();
window.onload = function() { d.resolve(); };
return d.promise;
}
O con una API como jQuery, conectando un evento que ocurre una vez:
function done() {
var d = $.Deferred();
$("#myObject").once("click",function() {
d.resolve();
});
return d.promise();
}
2. Devolución de llamada simple:
Estas API son bastante comunes ya que bueno ... las devoluciones de llamada son comunes en JS. Veamos el caso común de tener onSuccess
y onFail
:
function getUserData(userId, onLoad, onFail) { …
Con las implementaciones de promesa modernas que son compatibles con el constructor Promise
como promesas nativas ES6:
function getUserDataAsync(userId) {
return new Promise(function(resolve, reject) {
getUserData(userId, resolve, reject);
});
}
Con bibliotecas que admiten diferido (Vamos a usar jQuery para este ejemplo aquí, pero también hemos usado $ q arriba):
function getUserDataAsync(userId) {
var d = $.Deferred();
getUserData(userId, function(res){ d.resolve(res); }, function(err){ d.reject(err); });
return d.promise();
}
jQuery también ofrece un $.Deferred(fn)
, que tiene la ventaja de permitirnos escribir una expresión que emula muy de cerca el new Promise(fn)
formulario new Promise(fn)
, de la siguiente manera:
function getUserDataAsync(userId) {
return $.Deferred(function(dfrd) {
getUserData(userId, dfrd.resolve, dfrd.reject);
}).promise();
}
Nota: Aquí explotamos el hecho de que los métodos de resolve
y reject
jQuery aplazados son "desmontables"; es decir. están vinculados a la instancia de un jQuery.Deferred (). No todas las libretas ofrecen esta característica.
3. Devolución de llamada de estilo de nodo ("nodeback"):
Las devoluciones de llamada de estilo de nodo (nodebacks) tienen un formato particular en el que las devoluciones de llamada son siempre el último argumento y su primer parámetro es un error. Primero prometemos uno manualmente:
getStuff("dataParam", function(err, data) { …
A:
function getStuffAsync(param) {
return new Promise(function(resolve, reject) {
getStuff(param, function(err, data) {
if (err !== null) reject(err);
else resolve(data);
});
});
}
Con los aplazados puede hacer lo siguiente (usemos Q para este ejemplo, aunque Q ahora admite la nueva sintaxis que debería preferir ):
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;
}
En general, no debe prometer cosas manualmente demasiado, la mayoría de las bibliotecas de promesas que fueron diseñadas teniendo en cuenta a Node, así como las promesas nativas en Node 8+, tienen un método incorporado para promisificar nodebacks. Por ejemplo
var getStuffAsync = Promise.promisify(getStuff); // Bluebird
var getStuffAsync = Q.denodeify(getStuff); // Q
var getStuffAsync = util.promisify(getStuff); // Native promises, node only
4. Una biblioteca completa con devoluciones de llamada de estilo de nodo:
Aquí no hay una regla de oro, los prometes uno por uno. Sin embargo, algunas implementaciones de promesa le permiten hacer esto de forma masiva, por ejemplo, en Bluebird, convertir una API de nodeback en una API de promesa es tan simple como:
Promise.promisifyAll(API);
O con promesas nativas en Nodo :
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}), {});
Notas:
- Por supuesto, cuando estás en un
.then
manejador no necesitas promisificar cosas. Devolver una promesa de un controlador.then
se resolverá o rechazará con el valor de esa promesa. Lanzar desde un controlador es también una buena práctica y rechazará la promesa: esta es la famosa promesa de seguridad de lanzamiento. - En un caso de
onload
real, debe usaraddEventListener
lugar deonX
.
Mi versión promisificada de una función de callback
de callback
es la función 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))
La función P
requiere que la firma de devolución de callback(error,result)
sea una callback(error,result)
.
No creo que la sugerencia window.onload
de @Benjamin funcione todo el tiempo, ya que no detecta si se llama después de la carga. Me han mordido muchas veces. Aquí hay una versión que siempre debería funcionar:
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 incluye una nueva API util.promisify()
que permite que las API estándar de estilo de devolución de llamada Node.js se util.promisify()
en una función que devuelve una Promesa. Un ejemplo del uso de util.promisify()
se muestra a continuación.
const fs = require(''fs'');
const util = require(''util'');
const readfile = util.promisify(fs.readFile);
readfile(''/some/file'')
.then((data) => { /** ... **/ })
.catch((err) => { /** ... **/ });
Puede usar Promise nativo en ES6, por ejemplo, tratar con 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
});
}
En este ejemplo, la Promesa no tiene ninguna razón para fallar, por lo que nunca se llama a reject()
.
Puede utilizar las promesas nativas de JavaScript con Node JS.
Enlace de código de 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
Puedes hacer algo como esto
// @flow
const toPromise = (f: (any) => void) => {
return new Promise<any>((resolve, reject) => {
try {
f((result) => {
resolve(result)
})
} catch (e) {
reject(e)
}
})
}
export default toPromise
Entonces utilízalo
async loadData() {
const friends = await toPromise(FriendsManager.loadFriends)
console.log(friends)
}
es6-promisify
convierte las funciones basadas en devolución de llamada en funciones basadas en Promesa.
const promisify = require(''es6-promisify'');
const promisedFn = promisify(callbackedFn, args);
Antes de convertir una función como promesa En Node.JS
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)
})
Después de convertirlo
var request = require(''request'');
var Promise = require(''bluebird'');
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)
})
En caso de que necesite manejar múltiples solicitudes
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)
});