JavaScript中的延迟和承诺(+ Ember.js示例)

邢硕
2023-12-01

Warning: this post is old and might not reflect the current state of the art

警告:此帖子过时,可能无法反映当前的最新状态

Check out my Promises guide and my async/await guide instead.

请查看我的Promises指南async / await指南

Promises are a relatively new approach to async management, and they can be really helpful to structure your code.

承诺是一种相对较新的异步管理方法,对构建代码很有帮助。

A Promise is an object representation of an event. In the course of its life, a Promise goes from a pending state, when it’s called, to a resolved or rejected state, when it’s been completed, or it could also stay pending forever and is never resolved.

承诺是事件的对象表示。 在承诺的整个生命过程中,它从被调用的待处理状态变为已完成的已解决或已拒绝状态,或者也可以永远待处理且从未解决。

It’s a sort of new approach to JavaScript events, but I think it generates way more readable code, and it’s less quirky. At the moment there are 2 slightly different main implementations of Promises in javascript: those libraries that follow the Promises/A spec, and jQuery.

这是一种处理JavaScript事件的新方法,但是我认为它可以生成更具可读性的代码,并且不那么古怪。 目前,JavaScript中Promises的主要实现有2个略有不同:遵循Promises / A规范的库和jQuery。

First I’ll take jQuery into consideration as it’s everywhere and I use it, so if you don’t want another external library, you can use it.

首先,我将考虑jQuery,因为它无处不在,并且会使用它,因此,如果您不想使用其他外部库,则可以使用它。

引入jQuery Promise (Introducing jQuery promises)

Let’s introduce the concept of Deferred. First, a Deferred is a Promise, with in addition the fact that you can trigger a Deferred (resolve or reject it), while with a Promise, you can only add callbacks and it will be triggered by something else. A Promise, if you want, is a ‘listen-only’ part of a Deferred.

让我们介绍Deferred的概念。 首先,Deferred是一个Promise,此外,您可以触发Deferred(解决或拒绝它),而Promise则只能添加回调,并且它会由其他方式触发。 如果您愿意,则Promise是Deferred的“仅侦听”部分。

A clear example of this is this function

一个明显的例子就是这个功能

var promise = $('div.alert').fadeIn().promise();

You can now add .done() & .fail() to handle the callbacks. This is just a call example, promises for animations have become a real deal in jQuery 1.8, also with callbacks for progress.

现在,您可以添加.done()和.fail()来处理回调。 这只是一个调用示例,动画的承诺在jQuery 1.8中已经成为现实,并且还带有进度回调。

Another example of a promise is an AJAX call:

许诺的另一个示例是AJAX调用:

var promise = $.get(url);
promise.done(function(data) {});

A deferred is something you create, set the callbacks and resolve, like:

延迟是您创建的东西,设置回调并解决,例如:

var deferred = new $.Deferred();
deferred.done(function(data) { console.log(data) });
deferred.resolve('some data');

The state of a deferred can be triggered using .resolve() or .reject(). Once a deferred state has been changed to one of the final stages (resolved/rejected), it can’t be changed any more.

可以使用.resolve()或.reject()来触发延迟状态。 一旦将延迟状态更改为最后一个阶段(已解决/已拒绝),就无法再对其进行更改。

var deferred = new $.Deferred();
deferred.state();  // "pending"
deferred.resolve();
deferred.state();  // "resolved"

We can attach the following callbacks to a promise:

我们可以将以下回调附加到Promise:

.done() //will run when the promise has been executed successfully
.fail() //will run when the promise has failed
.always() //will run in either cases

Those callbacks can be called together using .then(), like:

可以使用.then()一起调用这些回调,例如:

promise.then(doneFunc, failFunc, alwaysFunc);

This is just an intro to the jQuery implementation of Promises and Deferreds. Let’s write some real-word examples. (if executing in node, you can import jQuery by using $ = require(‘jquery’); )

这只是Promises和Deferreds的jQuery实现的介绍。 让我们写一些真实的例子。 (如果在节点中执行,则可以使用$ = require('jquery');导入jQuery)

一些jQuery示例 (Some jQuery examples)

For example, here we execute a function, and when it’s finished it calls dfd.resolve(). Similar to doing a callback, but more structured and reusable.

例如,在这里我们执行一个函数,完成后将调用dfd.resolve()。 与执行回调类似,但更具结构性和可重用性。

$.when(execution()).then(executionDone);

function execution(data) {
  var dfd = $.Deferred();
  console.log('start execution');

  //in the real world, this would probably make an AJAX call.
  setTimeout(function() { dfd.resolve() }, 2000);

  return dfd.promise();
}

function executionDone(){
  console.log('execution ended');
}

Here the elements of an array are processed, and once all of them are fine (e.g. a request has returned), I call another function. We start to see the real benefits of the Deferred usage. The $.when.apply() method is used to group the dfd.resolve() in the loop.

这里处理数组的元素,一旦所有元素都很好(例如,请求已返回),我将调用另一个函数。 我们开始看到延迟使用的真正好处。 $ .when.apply()方法用于在循环中对dfd.resolve()进行分组。

var data = [1,2,3,4]; // the ids coming back from serviceA
var processItemsDeferred = [];

for(var i = 0; i < data.length; i++){
  processItemsDeferred.push(processItem(data[i]));
}

$.when.apply($, processItemsDeferred).then(everythingDone);

function processItem(data) {
  var dfd = $.Deferred();
  console.log('called processItem');

  //in the real world, this would probably make an AJAX call.
  setTimeout(function() { dfd.resolve() }, 2000);

  return dfd.promise();
}

function everythingDone(){
  console.log('processed all items');
}

A slightly more complex example, here the elements of the array are fetched from an external resource, using var fetchItemIdsDeferred = fetchItemIds(data) and fetchItemIdsDeferred.done()

稍微复杂一点的示例,此处使用var fetchItemIdsDeferred = fetchItemIds(data)和fetchItemIdsDeferred.done()从外部资源获取数组的元素。

var data = []; // the ids coming back from serviceA
var fetchItemIdsDeferred = fetchItemIds(data); // has to add the ids to data

function fetchItemIds(data){
  var dfd = $.Deferred();
  console.log('calling fetchItemIds');

  data.push(1);
  data.push(2);
  data.push(3);
  data.push(4);

  setTimeout(function() { dfd.resolve() }, 1000);
  return dfd.promise();
}

fetchItemIdsDeferred.done(function() { // if fetchItemIds successful...
  var processItemsDeferred = [];

  for(var i = 0; i < data.length; i++){
    processItemsDeferred.push(processItem(data[i]));
  }

  $.when.apply($, processItemsDeferred).then(everythingDone);
});


function processItem(data) {
  var dfd = $.Deferred();
  console.log('called processItem');

  //in the real world, this would probably make an AJAX call.
  setTimeout(function() { dfd.resolve() }, 2000);

  return dfd.promise();
}

function everythingDone(){
  console.log('processed all items');
}

Those last 2 examples explain how to compute a for cycle and then wait for the end of the processing execution to do something.

最后两个示例说明了如何计算for周期,然后等待处理执行结束以执行某项操作。

It’s the less “hacky” way of doing this:

这是不太“ hacky”的方式:

var allProcessed = false;
var countProcessed = 0;
for (var i = 0, len = theArray.length; i < len; i++) {
  (function(i) {
    // do things with i
        if (++countProcessed === len) allProcessed = true;
  })(i);
}

Now another example of how Deferreds can be used for: take a look at this

现在再来看看如何使用Deferreds的另一个例子:看看这个

var interval = setInterval(function() {
  if (App.value) {
    clearInterval(interval);
    // do things
  }
}, 100);

This is a construct that evaluates a condition; if the condition is true, the code clears the interval and executes the code contained in the if.

这是一个评估条件的构造; 如果条件为真,则代码清除间隔并执行if中包含的代码。

This is useful for example to check when a value is not undefined any more:

例如,这对于检查何时不再未定义值很有用:

var DeferredHelper = {
  objectVariableIsSet: function(object, variableName) {
    var dfd = $.Deferred();

    var interval = setInterval(function() {
      if (object[variableName] !== undefined) {
        clearInterval(interval);
        console.log('objectVariableIsSet');
        dfd.resolve()
      }
    }, 10);

    return dfd.promise();
  },

  arrayContainsElements: function(array) {
    var dfd = $.Deferred();

    var interval = setInterval(function() {
      if (array.length > 0) {
        clearInterval(interval);
        console.log('arrayContainsElements');
        dfd.resolve()
      }
    }, 10);

    return dfd.promise();
  }
}

var executeThis = function() {
  console.log('ok!');
}

var object = {};
object.var = undefined;
var array = [];

$.when(DeferredHelper.arrayContainsElements(array)).then(executeThis);
$.when(DeferredHelper.objectVariableIsSet(object, 'var')).then(executeThis);

setTimeout(function() {
  object.var = 2;
  array.push(2);
  array.push(3);
}, 2000);

The above example is in fact 3 examples in one. I created a DeferredHelper object and its methods arrayContainsElements and objectVariableIsSet are self-explaining.

上面的示例实际上是三个示例合二为一。 我创建了DeferredHelper对象,其方法arrayContainsElements和objectVariableIsSet是不言自明的。

Keep in mind that primitive types are passed by value, so you can’t do

请记住,原始类型是通过值传递的,因此您不能

var integerIsGreaterThanZero = function(integer) {
  var dfd = $.Deferred();

  var interval = setInterval(function() {
    if (integer > 0) {
      clearInterval(interval);
      dfd.resolve()
    }
  }, 10);

  return dfd.promise();
};

var variable = 0;

$.when(integerIsGreaterThanZero(variable)).then(executeThis);

nor you can do

你也不能做

var object = null;

var variableIsSet = function(object) {
  var dfd = $.Deferred();

  var interval = setInterval(function() {
    if (object !== undefined) {
      clearInterval(interval);
      console.log('variableIsSet');
      dfd.resolve()
    }
  }, 10);

  return dfd.promise();
};

$.when(variableIsSet(object)).then(executeThis);

setTimeout(function() {
  object = {};
}, 2000);

because when doing object = {}, the object reference is changed, and as Javascript actually references variables by copy-reference, the reference of the object variable inside the variableIsSet function is not the same as the outer object variable.

因为在执行object = {}时,对象引用会更改,并且由于Javascript实际上是通过复制引用来引用变量,所以variableIsSet函数内部的对象变量的引用与外部对象变量不同。

一个ember.js示例 (An ember.js example)

A thing I use with Ember.js is

我在Ember.js中使用的是

App.DeferredHelper = {

  /**
    * Check if an array has elements on the App global object if object
    * is not set.
    * If object is set, check on that object.
    */
  arrayContainsElements: function(arrayName, object) {
    var dfd = $.Deferred();
    if (!object) object = App;

    var interval = setInterval(function() {
      if (object.get(arrayName).length > 0) {
        clearInterval(interval);
        dfd.resolve()
      }
    }, 50);

    return dfd.promise();
  },

  /**
    * Check if a variable is set on the App global object if object
    * is not set.
    * If object is set, check on that object.
    */
  variableIsSet: function(variableName, object) {
    var dfd = $.Deferred();
    if (!object) object = App;

    var interval = setInterval(function() {
      if (object.get(variableName) !== undefined) {
        clearInterval(interval);
        dfd.resolve()
      }
    }, 50);

    return dfd.promise();
  }
}

so I can do in my client code:

所以我可以在我的客户代码中做:

$.when(App.DeferredHelper.arrayContainsElements('itemsController.content'))
  .then(function() {
  //do things
});

and

$.when(App.DeferredHelper.variableIsSet('aVariable'))
  .then(function() {
  //do things
});

//&

$.when(App.DeferredHelper.variableIsSet('aVariable', anObject))
  .then(function() {
  //do things
});

All those examples were made using the jQuery deferreds implementation.

所有这些示例都是使用jQuery延迟实现实现的。

If you’re not willing to use the jQuery deferred implementation, maybe because you’re not using jQuery and loading it just for the deferreds is overkill, or you’re using another library that does not have a deferred implementation, you can use other libraries specialized in this, such as Q, rsvp.js, when.js.

如果您不愿意使用jQuery延迟实现,则可能是因为您不使用jQuery并仅仅为延迟而加载它是过大的,或者您正在使用没有延迟实现的另一个库,则可以使用其他专门用于此的库,例如Qrsvp.jswhen.js

让我们使用when.js编写一些示例 (Let’s write some examples using when.js)

For example, I have the ID of an item, and I want to call the API endpoint to get more detail about it. Once the AJAX call returns, continue processing.

例如,我有一个项目的ID,并且我想调用API端点以获取有关它的更多详细信息。 一旦AJAX调用返回,请继续处理。

function processItem(item) {
  var deferred = when.defer();

  var request = $.ajax({
    url: '/api/itemDetails',
    type: 'GET'
    data: {
      item: item
    }
  });

  request.done(function(response) {
    deferred.resolve(JSON.parse(response));
  });

  request.fail(function(response) {
    deferred.reject('error');
  });

  return deferred.promise;
}

var item = {
  id: 1
}

processItem(item).then(
  function gotIt(itemDetail) {
    console.log(itemDetail);
  },
  function doh(err) {
    console.error(err);
  }
);

I got some ID values from a server, process them using the processItem() function from above, and then once finished processing ALL of them, I can do something

我从服务器上获得了一些ID值,从上方使用processItem()函数对其进行处理,然后在完成对所有ID的处理之后,我可以做一些事情

function processItems(anArray) {
  var deferreds = [];

  for (var i = 0, len = anArray.length; i < len; i++) {
    deferreds.push(processItem(anArray[i].id));
  }

  return when.all(deferreds);
}

var anArray = [1, 2, 3, 4];

processItems(anArray).then(
  function gotEm(itemsArray) {
    console.log(itemsArray);
  },
  function doh(err) {
    console.error(err);
  }
);

The when.js library provides some utility methods such as when.any() and when.some(), that let the deferred callback run when 1) one of the promises has been solved 2) at least a specified number of promises have returned.

when.js库提供了一些实用程序方法,例如when.any()和when.some(),它们使延迟的回调在以下情况下运行:1)已解决一个诺言2)至少返回了指定数目的诺言。

翻译自: https://flaviocopes.com/deferreds-and-promises-in-javascript/

 类似资料: