Javascript 异步发电机中由于承诺的非并行等待而导致的减速

Javascript 异步发电机中由于承诺的非并行等待而导致的减速,javascript,async-await,promise,generator,bluebird,Javascript,Async Await,Promise,Generator,Bluebird,我正在使用生成器和Bluebird编写代码,我有以下几点: var async = Promise.coroutine; function Client(request){ this.request = request; } Client.prototype.fetchCommentData = async(function* (user){ var country = yield countryService.countryFor(user.ip); var dat

我正在使用生成器和Bluebird编写代码,我有以下几点:

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
    };
});
<-client     service->
countryFor..
           ''--..
              ''--..
                 ''--.. country server sends response
               ..--''
          ..--''
     ..--''
getCommentDataFor
     ''--..
           ''--..
               ''--..
                     ''--.. comment service returns response
                ..--''
          ..--''
      ..--''
authenticate
       ''--..
            ''--..
                  ''--.. authentication service returns
             ..--''
       ..--''
 ..--''
 Generator done.
但是,这有点慢,我觉得我的应用程序等待I/O的时间太长了,而且它不是并行的。如何提高应用程序的性能


对于
countryFor
+400对于
getCommentDataFor
+600对于
authenticate
而言,总响应时间为800,因此总计1800ms,这是一个很大的问题。

您花费了太多时间等待来自不同来源的I/O

在普通的promise代码中,您会使用
promise.all
,然而,人们倾向于编写等待生成器请求的代码。您的代码执行以下操作:

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
    };
});
<-client     service->
countryFor..
           ''--..
              ''--..
                 ''--.. country server sends response
               ..--''
          ..--''
     ..--''
getCommentDataFor
     ''--..
           ''--..
               ''--..
                     ''--.. comment service returns response
                ..--''
          ..--''
      ..--''
authenticate
       ''--..
            ''--..
                  ''--.. authentication service returns
             ..--''
       ..--''
 ..--''
 Generator done.
这是人们第一次使用发电机时经常犯的错误


ascii艺术无耻地取自Kris Kowal的Q-Connection

正如蓝鸟文档中提到的,您需要注意不要在一系列中产生

此代码有3个
yield
表达式,每个表达式都停止执行,直到特定的承诺得到解决。代码将连续创建和执行每个异步任务

要并行等待多个任务,您应该
产生
一系列承诺。这将等待所有这些问题解决,然后返回一个结果值数组。使用ES6解构分配可以得到简洁的代码:

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

Benjamin Gruenbaum的答案是正确的,但是它完全失去了生成器方面的功能,当您尝试并行运行多个事物时,这种情况往往会发生。但是,您可以使用
yield
关键字使这项工作正常进行。我还使用了一些额外的ES6功能,如和:

如果您不想使用这些额外的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]
    };
});

+1不仅为了这个答案的清晰,而且为了第三人称引用。@Bergi在generators中执行
返回
实际上就像
屈服
一样,只有
完成
标志也设置为true,所以从技术上讲它使用Generator。我同意发电机不再有用了。我问了这个问题,因为我看到人们一直在发电机上错误地使用这个用例,发电机的一个更复杂的例子觉得cruft不值得教导,我仍然愿意接受改进建议。是
countryService.countryFor(user.ip)
同步网络吗?我看不到任何回调或承诺会允许它异步。@BenjaminGruenbaum:我会给出一个答案:-)@Bergi一定要这样做:)+1尽管节点11没有这样的分解结构Bluebird是否支持生成一个开箱即用的承诺数组?@rane:在当前版本中看起来不像。请看最后一个添加此功能的示例,如果您不喜欢,可以使用
Promise。所有
我认为下面的示例最好使用
Promise.props
来表达,如上所示。您是对的。我只是坚持ES6 API的承诺,因为这是我真正知道的。我们能不能想出一个更好的标题,类似于“异步生成器中并行运行承诺”?@Bergi,一定要这样做。只是我不喜欢“运行承诺”这个短语,而且我还想加入性能方面的东西。你能想出更好的方法吗?是的,承诺在任何程度上都不是“运行”的,但我教人们代码和回答问题的时间越长,我就越不在乎确切的术语,而更看重有用性。这里的目标是让人们知道生成器在这些场景中可能会很慢,并让人们知道一个常见的性能缺陷,任何能够更好地达到或实现该目标的东西都是积极的IMO@Bergi!NotBanked表示用户被禁止?B/c然后返回notbanked:true,则相反,否?
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 };
});
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]
    };
});