Desaceleração devido a espera não paralela de promises em geradores asynchronouss

Estou escrevendo código usando geradores e Bluebird e tenho o seguinte:

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 }; }); 

No entanto, isso é meio lento, sinto que meu aplicativo está esperando muito por E / S e não está em paralelo. Como posso melhorar o desempenho do meu aplicativo?

O tempo total de resposta é de 800 para countryFor + 400 para getCommentDataFor + 600 para authenticate , totalizando 1800ms, o que é muito.

Você está gastando muito tempo esperando por E / S de diferentes fonts.

No código promise normal, você usaria Promise.all para isso, no entanto – as pessoas têm uma tendência a escrever código que aguarda solicitações com geradores. Seu código faz o seguinte:

 < -client service-> countryFor.. ''--.. ''--.. ''--.. country server sends response ..--'' ..--'' ..--'' getCommentDataFor ''--.. ''--.. ''--.. ''--.. comment service returns response ..--'' ..--'' ..--'' authenticate ''--.. ''--.. ''--.. authentication service returns ..--'' ..--'' ..--'' Generator done. 

Em vez disso, deveria estar fazendo:

 < -client service-> countryFor.. commentsFor..''--.. authenticate..''--..''--.. ''--..''--..''--.. country server sends response ''--..--''.. comment service returns response ..--''..--''.. authentication service returns response ..--''..--''.. ..--''..--''..--'' ..--''..--'' ..--'' Generator done 

Simplificando, todo o seu I / O deve ser feito em paralelo aqui.

Para corrigir isso, eu usaria Promise.props . Promise.props pega objects e espera que todas as suas propriedades sejam resolvidas (se forem promises).

Lembre-se – geradores e promises combinam e combinam muito bem, você simplesmente produz promises:

 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 é um erro muito comum que as pessoas cometem ao usar geradores pela primeira vez.

arte ascii descaradamente tomada de Q-Connection por Kris Kowal

Como é mencionado nos documentos do Bluebird para Promise.coroutine , você precisa estar atento para não yield em uma série.

 var county = yield countryService.countryFor(user.ip); var data = yield api.getCommentDataFor(user.id); var notBanned = yield authServer.authenticate(user.id); 

Esse código tem 3 expressões de yield , cada uma delas interrompendo a execução até que a promise específica seja estabelecida. O código irá criar e executar cada uma das tarefas assíncronas consecutivamente.

Para esperar várias tarefas em paralelo, você deve yield uma série de promises . Isso aguardará até que todos sejam resolvidos e, em seguida, retornará uma matriz de valores de resultado. O uso de atribuições de desestruturação do ES6 leva a um código conciso para isso:

 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 }; }); 

A resposta de Benjamin Gruenbaum está correta, mas perde completamente o aspecto dos geradores, o que tende a acontecer um pouco quando você está tentando executar várias coisas em paralelo. Você pode, no entanto, fazer isso funcionar muito bem com a palavra-chave yield . Eu também estou usando alguns resources extra do ES6 como atribuições de desestruturação e abreviação do inicializador de object :

 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 }; }); 

Se você não quiser que esses resources extras do ES6 sejam usados:

 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] }; });