对未使用延迟[反]模式而尚未创建的承诺的承诺

mde*_*erk 7 javascript promise deferred

问题1:在给定时间只允许一个API请求,因此真实的网络请求排队,而尚未完成的请求.应用程序可以随时调用API级别并期望获得回报.当API调用排队时,将在未来的某个时刻创建网络请求的承诺 - 返回应用程序的内容是什么?这就是如何通过延迟的"代理"承诺来解决它:

var queue = [];
function callAPI (params) {
  if (API_available) {
    API_available = false;
    return doRealNetRequest(params).then(function(data){
      API_available = true;
      continueRequests();
      return data;
    });
  } else {
    var deferred = Promise.defer();
    function makeRequest() {
      API_available = false;
      doRealNetRequest(params).then(function(data) {
        deferred.resolve(data);
        API_available = true;
        continueRequests();
      }, deferred.reject);
    }
    queue.push(makeRequest);
    return deferred.promise;
  }
}

function continueRequests() {
  if (queue.length) {
    var makeRequest = queue.shift();
    makeRequest();
  }
}
Run Code Online (Sandbox Code Playgroud)

问题2:某些API调用被去抖动,因此要发送的数据会随着时间的推移而累积,然后在达到超时时批量发送.调用API的应用程序期待作为回报的承诺.

var queue = null;
var timeout = 0;
function callAPI2(data) {
  if (!queue) {
    queue = {data: [], deferred: Promise.defer()};
  }
  queue.data.push(data);
  clearTimeout(timeout);
  timeout = setTimeout(processData, 10);
  return queue.deferred.promise;
}

function processData() {
  callAPI(queue.data).then(queue.deferred.resolve, queue.deferred.reject);
  queue = null;
}
Run Code Online (Sandbox Code Playgroud)

由于deferred被认为是反模式,(参见何时需要创建延迟?),问题是 - 是否可以new Promise(function (resolve, reject) {outerVar = [resolve, reject]});使用标准的Promise API 实现相同的事情而不会延迟(或等效的hacks ) ?

Ber*_*rgi 5

尚未创建的承诺的承诺

...通过将then调用与创建承诺的回调链接起来很容易构建,承诺表示将来创建它的可用性。

如果您要为承诺做出承诺,则永远不应使用延迟模式。Promise当且仅当您想要等待一些异步操作,并且它尚未涉及 promises 时,您才应该使用 deferreds 或构造函数。在所有其他情况下,您应该组合多个承诺。

当你说

当 API 调用排队时,网络请求的承诺将在未来的某个时刻创建

那么你不应该创建一个延迟,你可以在创建后用承诺解决它(或者更糟的是,一旦承诺解决,就用承诺结果解决它),而是你应该得到一个未来点的承诺将进行网络请求。基本上你要写

return waitForEndOfQueue().then(makeNetworkRequest);
Run Code Online (Sandbox Code Playgroud)

当然,我们需要分别改变队列。

var queue_ready = Promise.resolve(true);
function callAPI(params) {
  var result = queue_ready.then(function(API_available) {
    return doRealNetRequest(params);
  });
  queue_ready = result.then(function() {
    return true;
  });
  return result;
}
Run Code Online (Sandbox Code Playgroud)

这有额外的好处,您需要显式处理队列中的错误。在这里,一旦一个请求失败,每个调用都会返回一个被拒绝的承诺(你可能想要改变它)——在你的原始代码中,queue刚刚卡住了(你可能没有注意到)。

第二种情况有点复杂,因为它确实涉及setTimeout调用。这是一个异步原语,我们需要为它手动构建一个承诺——但仅限于超时,没有别的。同样,我们将获得超时承诺,然后简单地将我们的 API 调用链接到该承诺以获取我们想要返回的承诺。

function TimeoutQueue(timeout) {
  var data = [], timer = 0;
  this.promise = new Promise(resolve => {
    this.renew = () => {
      clearTimeout(timer);
      timer = setTimeout(resolve, timeout);
    };
  }).then(() => {
    this.constructor(timeout); // re-initialise
    return data;
  });
  this.add = (datum) => {
    data.push(datum);
    this.renew();
    return this.promise;
  };
}

var queue = new TimeoutQueue(10);
function callAPI2(data) {
  return queue.add(data).then(callAPI);
}
Run Code Online (Sandbox Code Playgroud)

您可以在这里看到 a) 如何完全排除去抖动逻辑callAPI2(这可能不是必需的,但提出了一个很好的观点)和 b) 承诺构造函数如何只关心超时而不关心其他任何事情。它甚至不需要resolve像延迟那样“泄漏”函数,它唯一可供外部使用的是renew允许扩展计时器的函数。