【问题标题】:Break a for loop with promises用 Promise 打破 for 循环
【发布时间】:2014-07-07 21:10:06
【问题描述】:

在我的节点 js 应用程序中,我有一个函数可以检查您是否有权限:

//queryPermissions is an object that contains all possible permissions:
//each property of queryPermission is an object containing the values to be checked
for (var key in queryPermissions) {
    if (queryPermissions.hasOwnProperty(key)) {
        promisesArray.push(checkThis(key, req.method));
    }
}

Q.all(promisesArray).then(function(response) {
    response.forEach(function(value) {
        //response is an array with all promises' resolved values
        //if all values are true -> access granted
        //if one or more values are false -> access denied
    }
}

这很好用,但如果 for 循环中 checkThis 返回的值只有一个为 false,则结果将被拒绝访问; 因此在第一个以 false 解决的权限之后继续检查其他权限是没有效率的; CheckThis 返回一个承诺,但有时它需要等待查询结果,有时它会立即解析,这取决于。

当第一个“checkThis”返回 false 时,有没有办法可以中断循环(如果还没有完成)?

【问题讨论】:

  • 这超出了异步性的目的,不是吗?
  • 正如thefourtheye 提到的,只有在每次请求后检查结果然后继续其他请求时,才能执行此操作。这会将您的异步代码转换为同步代码。我假设checkThis 函数向数据库(或其他远程服务器)发出请求。在这种情况下,只需发出一个具有所有检查权限的请求会更容易。在这种情况下,您可以在另一端进行优化。如果您不能更改另一端,那么恐怕您不会使该代码更有效率。
  • 在这种情况下,不可能同时请求所有权限,因为每个权限(如果需要)都必须从数据库中找到一些不同的数据;所以在任何情况下,每个人的请求都会有所不同......
  • 您可以尝试在您的 checkThis 中抛出异常。我想它会停止流动。但是无论如何,如果所有的 Promise 都已经运行,你几乎不会保存任何东西。如果 Promise 中的每个调用都需要大量资源,请考虑像前面提到的那样切换到同步调用。

标签: javascript node.js promise q


【解决方案1】:

这是 Bergi 方法的替代解决方案 - 我们将错误的返回值映射到异常条件失败,并直接使用 Q.all:

您当前的代码可以:

for (var key in queryPermissions) {
    if (queryPermissions.hasOwnProperty(key)) {
        promisesArray.push(checkThis(key, req.method));
    }
}

我们添加了一个额外的步骤:

for (var key in queryPermissions) {
    if (queryPermissions.hasOwnProperty(key)) {
        promisesArray.push(checkThis(key, req.method).then(function(val){
             if(!val) throw new Error("Invalid Permissions");
             return true;
        });
    }
}

这个简单的加法让我们可以直接使用 Q:

Q.all(promisesArray).catch(function(err){
     // one or more authentication errors
}).then(function(){
      // everyone validated, all ok user authenticated here
});

这是一种更通用的方法——对异常情况使用异常管道可以大大简化您的代码。

【讨论】:

    【解决方案2】:

    不适用于任何本机 Q 函数。但是,您可以自己为 promises 写一个every method(大致基于Q.all):

    Q.every = function every(promises) {
        return Q.when(promises, function (promises) {
            var countDown = 0;
            var deferred = defer();
            for (var i=0; i<promises.length; i++) {
                var promise = promises[i];
                var snapshot;
                if (
                    Q.isPromise(promise) &&
                    (snapshot = promise.inspect()).state === "fulfilled"
                ) {
                    if (!snapshot.value) {
                        deferred.resolve(false);
                        return deferred.promise;
                    }
                } else {
                    ++countDown;
                    q.when(
                        promise,
                        function (value) {
                            if (!value)
                                deferred.resolve(false);
                            else if (--countDown === 0)
                                deferred.resolve(true);
                        },
                        deferred.reject,
                        (function(index) {
                            return function (progress) {
                                deferred.notify({ index: index, value: progress });
                            };
                        }(i));
                    );
                }
            }
            if (countDown === 0) {
                deferred.resolve(true);
            }
            return deferred.promise;
        });
    }
    

    【讨论】:

    • 努力+1,但是如果异步代码已经执行,那么这样做没有意义,对吧?
    • 我在上面找到并发布的解决方案有效;但无论如何这很有趣,并且可以在其他地方有用......
    • @thefourtheye:如果产生false 的promise 之一比其他promise 快,我们可以更早地解决产生的promise。当然,这不会中止其他承诺(部分是因为我懒得实现它,部分是因为 Q 不支持它)。
    • @BenjaminGruenbaum:谢谢你的好收获。这是从original code 中删除reduce_array 调用的剩余部分。
    【解决方案3】:

    这是不可能的。事实上,Q.all 运行并行承诺,所以当你想打破时,其他承诺仍在运行。所以他们不能被阻止。一种方法是使用Q.spread,但因此承诺不再平行:(。

    【讨论】:

      【解决方案4】:

      也许我有一个突然的启蒙,但并不那么难: 只使用一个全局标志:

      var alreadyFailed = false;
      
      //queryPermissions is an object that contains all possible permissions:
      //each property of queryPermission is an object containing the values to be checked
      for (var key in queryPermissions) {
          if (alreadyFailed) {
              break;
          }
          if (queryPermissions.hasOwnProperty(key)) {
              promisesArray.push(checkThis(key, req.method));
          }
      }
      

      那么当发生错误结果时,在 checkThis 中将 alreadyFailed 设置为 true 就足够了; 当然,for 循环可能会在第一次解析为 false 之前结束,但在这种情况下,没有办法做得更好; 但是如果 checkThis 在 for 循环结束之前解析为 false -> 它将被停止

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-03-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-08-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多