javascript - secuencial - Desaceleración debido a la no paralela espera de promesas en generadores asíncronos
programacion asincrona javascript (3)
Estoy escribiendo código usando generadores y Bluebird y tengo lo siguiente:
var async = Promise.coroutine;
function Client(request){
this.request = request;
}
Client.prototype.fetchCommentData = async(function* (user){
var country = yield countryService.countryFor(user.ip);
var data = yield api.getCommentDataFor(user.id);
var notBanned = yield authServer.authenticate(user.id);
if (!notBanned) throw new AuthenticationError(user.id);
return {
country: country,
comments: data,
notBanned: true
};
});
Sin embargo, esto es un poco lento, siento que mi aplicación está esperando demasiado por I / O y no está en paralelo. ¿Cómo puedo mejorar el rendimiento de mi aplicación?
El tiempo total de respuesta es de 800 para countryFor
+ 400 para getCommentDataFor
+ 600 para la authenticate
por lo que, en total, 1800 ms, es mucho.
Como se menciona en los documentos de Bluebird para Promise.coroutine
, debe tener cuidado de no yield
en una serie.
var county = yield countryService.countryFor(user.ip); var data = yield api.getCommentDataFor(user.id); var notBanned = yield authServer.authenticate(user.id);
Este código tiene 3 expresiones de yield
, cada una de las cuales detiene la ejecución hasta que se resuelva la promesa en particular. El código creará y ejecutará cada una de las tareas asíncronas consecutivamente.
Para esperar múltiples tareas en paralelo, debe yield
una serie de promesas . Esto esperará hasta que todos se hayan resuelto, y luego devolverá una matriz de valores de resultados. El uso de las asignaciones de desestructuración de ES6 conduce a un código conciso para eso:
Client.prototype.fetchCommentData = async(function* (user){
var [county, data, notBanned] = yield [
// a single yield only: ^^^^^
countryService.countryFor(user.ip),
api.getCommentDataFor(user.id),
authServer.authenticate(user.id)
];
if (!notBanned)
throw new AuthenticationError(user.id);
return {
country: country,
comments: data,
notBanned: true
};
});
Está pasando demasiado tiempo esperando la E / S de diferentes fuentes.
Sin embargo, en el código de promesa normal, usaría Promise.all
para esto, las personas tienen una tendencia a escribir código que espera solicitudes con generadores. Su código hace lo siguiente:
<-client service->
countryFor..
''''--..
''''--..
''''--.. country server sends response
..--''''
..--''''
..--''''
getCommentDataFor
''''--..
''''--..
''''--..
''''--.. comment service returns response
..--''''
..--''''
..--''''
authenticate
''''--..
''''--..
''''--.. authentication service returns
..--''''
..--''''
..--''''
Generator done.
En su lugar, debería estar haciendo:
<-client service->
countryFor..
commentsFor..''''--..
authenticate..''''--..''''--..
''''--..''''--..''''--.. country server sends response
''''--..--''''.. comment service returns response
..--''''..--''''.. authentication service returns response
..--''''..--''''..
..--''''..--''''..--''''
..--''''..--''''
..--''''
Generator done
En pocas palabras, toda su E / S debe hacerse en paralelo aquí.
Para arreglar esto, usaría Promise.props
. Promise.props
toma un objeto y espera a que todas sus propiedades se resuelvan (si son promesas).
Recuerde: los generadores y las promesas se combinan y combinan realmente bien, usted simplemente promete:
Client.prototype.fetchCommentData = async(function* (user){
var country = countryService.countryFor(user.ip);
var data = api.getCommentDataFor(user.id);
var notBanned = authServer.authenticate(user.id).then(function(val){
if(!val) throw new AuthenticationError(user.id);
});
return Promise.props({ // wait for all promises to resolve
country : country,
comments : data,
notBanned: notBanned
});
});
Este es un error muy común que las personas cometen cuando usan generadores por primera vez.
Arte ascii descaradamente tomado de Q-Connection por Kris Kowal
La respuesta de Benjamin Gruenbaum es correcta, pero pierde el aspecto de los generadores por completo, lo que suele suceder un poco cuando intentas ejecutar varias cosas en paralelo. Sin embargo, puede hacer que esto funcione bien con la palabra clave de yield
. También estoy usando algunas características adicionales de ES6 como desestructurar asignaciones y la abreviatura de inicializador de objetos :
Client.prototype.fetchCommentData = async(function* (user){
var country = countryService.countryFor(user.ip);
var data = api.getCommentDataFor(user.id);
var notBanned = authServer.authenticate(user.id).then(function(val){
if(!val) throw new AuthenticationError(user.id);
});
// after each async operation finishes, reassign the actual values to the variables
[country, data, notBanned] = yield Promise.all([country, data, notBanned]);
return { country, data, notBanned };
});
Si no quieres que se utilicen esas características adicionales de ES6:
Client.prototype.fetchCommentData = async(function* (user){
var country = countryService.countryFor(user.ip);
var data = api.getCommentDataFor(user.id);
var notBanned = authServer.authenticate(user.id).then(function(val){
if(!val) throw new AuthenticationError(user.id);
});
var values = yield Promise.all([country, data, notBanned]);
return {
country: values[0],
data: values[1],
notBanned: values[2]
};
});