首页 文章

如何在动态的承诺列表中等待最后的承诺?

提问于
浏览
4

我有一个函数F,它启动一个异步进程X.该函数返回一个在X结束时解析的promise(我通过X返回的promise来学习) .

当(w.l.o.g.)X,X1的第一个实例正在运行时,可能会有更多对F的调用 . 每个调用都会产生一个新的X实例,例如: X2,X3等 .

Now, here's the difficulty: 创建X2时,根据X1的状态,X1应该结束或中止 . 只有X1不再处于活动状态时,X2才会开始工作 . In any case, the unresolved promises returned from all previous calls to F should be resolved only once X2 has concluded, as well - or, any later instance of X, if F gets called again while X2 is running.

到目前为止,对F的第一次调用调用 $q.defer() 来创建一个延迟,其承诺由对F的所有调用返回,直到最后一个X结束 . (然后,应该解析延迟,并且保存它的字段应该重置为null,等待下一个对F的调用集群 . )

现在,我的问题是等到X的所有实例都完成了 . 我知道如果事先有完整的X实例列表,我可以使用 $q.all 但是因为我必须考虑以后调用F,所以这不是解决方案 . 理想情况下,我可能应该将一些事情转换为X返回的承诺以解决延迟的问题,并且只要我将其链接到X的后续实例,就可以使用"unchain"该功能 .

我想像这样的事情:

var currentDeferred = null;

function F() {
    if (!currentDeferred) {
        currentDeferred = $q.defer();
    }

    // if previous X then "unchain" its promise handler
    X().then(function () {
        var curDef = currentDeferred;
        currentDeferred = null;
        curDef.resolve();
    });

    return currentDeferred.promise;
}

但是,如果这是一个正确的解决方案,我不知道如何执行“不加协调” .

How do I go about this? Am I missing some common pattern or even built-in feature of promises, or am I on the wrong track altogether?


添加一些上下文:调用F来加载数据(异步)并更新一些可视输出 . F返回一个只有在视觉输出再次更新到稳定状态(即没有更多待更新)时才能解析的promise .

1 回答

  • 1

    调用F来加载数据(异步)并更新一些视觉输出 . F返回一个只有在视觉输出再次更新到稳定状态(即没有更多待更新)时才能解析的promise .

    由于 F 的所有呼叫者都将收到他们需要消费的承诺,但您只想在所有堆叠呼叫完成后更新UI,最简单的方法是让每个承诺解决(或拒绝)一个值,告诉呼叫者不要如果有另一个"get more data"呼叫待处理,则更新UI;这样,只有其承诺最后解析的调用者才会更新UI . 你可以通过跟踪未完成的电话来做到这一点:

    let accumulator = [];
    let outstanding = 0;
    function F(val) {
      ++outstanding;
      return getData(val)
        .then(data => {
          accumulator.push(data);
          return --outstanding == 0 ? accumulator.slice() : null;
        })
        .catch(error => {
          --outstanding;
          throw error;
        });
    }
    
    // Fake data load
    function getData(val) {
      return new Promise(resolve => {
        setTimeout(resolve, Math.random() * 500, "data for " + val);
      });
    }
    
    let accumulator = [];
    let outstanding = 0;
    function F(val) {
      ++outstanding;
      return getData(val)
        .then(data => {
          accumulator.push(data);
          return --outstanding == 0 ? accumulator.slice() : null;
        })
        .catch(error => {
          --outstanding;
          throw error;
        });
    }
    
    // Resolution and rejection handlers for our test calls below
    const resolved = data => {
      console.log("chain done:", data ? ("update: " + data.join(", ")) : "don't update");
    };
    const rejected = error => { // This never gets called, we don't reject
      console.error(error);
    };
    
    // A single call:
    F("a").then(resolved).catch(rejected);
    
    setTimeout(() => {
      // One subsequent call
      console.log("----");
      F("b1").then(resolved).catch(rejected);
      F("b2").then(resolved).catch(rejected);
    }, 600);
    
    setTimeout(() => {
      // Two subsequent calls
      console.log("----");
      F("c1").then(resolved).catch(rejected);
      F("c2").then(resolved).catch(rejected);
      F("c3").then(resolved).catch(rejected);
    }, 1200);
    
    .as-console-wrapper {
      max-height: 100% !important;
    }
    

    (这是本机承诺;根据 $q 的需要进行调整 . )

    对我来说,"don't update"与"failed,"不同,所以我使用了一个标志值( null ),而不是拒绝信号 . 但是当然,你也可以使用带有标志值的拒绝,它在 catch 处理程序中更新“?”而不是你的 then [这是真实的数据吗?] ......嗯,我可能会走另一条路现在我想到了 . 但那是微不足道的改变 . )

    显然,上面的 accumulator 只是您真实数据结构的原始占位符(并且它不会尝试按照请求的顺序保持接收的数据) .

    我有承诺解决上面的数据副本( accumulator.slice() ),但在您的情况下可能没有必要 .

相关问题