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

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

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

现在,困难在于:当创建X2时,根据X1的状态,X1应该结束或中止.只有X1不再处于活动状态时,X2才会开始工作.在任何情况下,从X的所有先前调用返回的未解析的promise应该只在X2结束时解决,或者,如果在X2运行时再次调用F,则X的任何后续实例都应该解析.

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

现在,我的问题是等到X的所有实例都完成了.我知道如果事先有完整的X实例列表,我可以使用$q.all,但是由于我必须考虑以后调用F,所以这不是解决方案.理想情况下,我应该把一些东西链接到X返回的promise以解决延迟,并且只要我将它链接到X的后续实例就“解链”.

我想像这样的事情:

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

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

我该怎么做?我是否缺少一些常见的模式甚至是承诺的内置功能,或者我是否完全错在了轨道上?

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

最佳答案

F is called to load data (asynchronously) and updating some visual output. F returns a promise that should only be resolved once the visual output is updated to a stable state again (i.e. with no more updates pending).

由于F的所有呼叫者都将收到他们需要消费的承诺,但您只想在所有堆叠呼叫完成后更新UI,最简单的方法是让每个承诺解决(或拒绝)一个值,告诉呼叫者不要如果有另一个“获取更多数据”呼叫待处理,则更新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.)

对我来说,“不更新”与“失败”不同,所以我使用了一个标志值(null)而不是拒绝来发信号.但是当然,您也可以使用带有标志值的拒绝,这取决于您. (这样可以将条件逻辑[这是一个真正的错误还是只是一个“不更新”?]放在你的catch处理程序而不是你的那个[这是真正的数据吗?] ……嗯,我现在可能会想到它.但这是微不足道的改变.)

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

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

点赞