Подтвердить что ты не робот

Как я могу определить, является ли объект jQuery Promise/Deferred?

У меня есть функция, которая принимает один аргумент. Мне нужно знать, является ли этот аргумент объектом jQuery Promise или Deferred. Если нет, то значение может быть любого типа и иметь любые свойства, поэтому оно небезопасно только для наличия методов обещаний.

Вот пример того, как я хочу, чтобы моя функция вела себя:

function displayMessage(message) {
  if (message is a Promise or Deferred) {
    message.then(displayMessage);
  } else {
    alert(message);
  }
}

Обратите внимание на рекурсивную обработку promises: если обещание разрешено с другим значением обещания, мы его не видим, мы ждем его разрешения. Если он вернет еще одно обещание, повторите.


Это важно, потому что, если бы это было не так, я бы просто использовал jQuery.when:

function displayMessage(message) {
  jQuery.when(message).then(function(messageString) {
    alert(messageString);
  });
}

Это будет корректно обрабатывать значения и promises значений...

displayMessage("hello");                            // alerts "hello"
displayMessage(jQuery.Deferred().resolve("hello")); // alerts "hello"

... но как только мы перейдем к promises из promises значений, он сломается:

displayMessage(jQuery.Deferred().resolve(
  jQuery.Deferred().resolve("hello")
));                                                 // alerts "[object Object]"

jQuery.when может сказать, обещано ли значение, так что, по-видимому, это возможно. Как я могу проверить?

4b9b3361

Ответ 1

jQuery.when может сказать, обещано ли значение, поэтому, по-видимому, это возможно.

Это ошибочно. Сам jQuery не может проверить, является ли объект обещанием с полной точностью. Если вы посмотрите на источник jQuery.when в средстве просмотра jQuery, вы увидите, что все, что он делает, это следующее:

jQuery.isFunction(firstParam.promise)

Если возвращаемый объект имеет свой собственный метод .promise(), jQuery.when будет ошибочным:

var trickyValue = {
  promise: function() { return 3; },
  value: 2
};

jQuery.when(trickyValue).then(function(obj) {
  alert(obj.value);
});

Это вызывает TypeError: Object 3 has no method 'then', потому что jQuery предполагает, что объект является обещанием и доверяет его методу .promise().

Это, вероятно, невозможно решить правильно. Объект-обещание создается как литерал объекта внутри jQuery.Deferred (источник просмотра). Он не имеет прототипа и других уникальных свойств, которые могли бы быть использованы для его отличия.

Однако я могу подумать о хакерском решении, которое должно быть надежным, если используется только одна версия jQuery:

function isPromise(value) {
  if (typeof value === 'object' && typeof value.then !== "function") {
    return false;
  }
  var promiseThenSrc = String($.Deferred().then);
  var valueThenSrc = String(value.then);
  return promiseThenSrc === valueThenSrc;
}

isPromise("test");                 // false
isPromise($.Deferred());           // true
isPromise($.Deferred().promise()); // true

Преобразование функции в строку дает вам исходный код, поэтому здесь я сравниваю исходный код метода .then нового объекта Deferred с объектом, который интересует меня. Ваша ценность не является будет иметь метод .then с точно таким же исходным кодом, как jQuery.Deferred или Promise 1.

1. Если вы не работаете во враждебной среде, в этом случае вы, вероятно, должны отказаться.


Если вас не интересует jQuery promises, но вы хотите обнаружить любой тип Promise, включая встроенные из ECMAScript 6, вы можете проверить, является ли значение объектом и имеет метод then

if (typeof value === 'object' && typeof value.then === 'function') {
  // handle a promise
} else {
  // handle a concrete value
}

Это подход нескольких функций управления обещаниями, определенных в ES6. Вы можете увидеть это описание в спецификации функций resolve(...), частично цитируемое ниже:

Когда обещание разрешает функцию F вызывается с разрешением аргумента, выполняются следующие шаги:

[...]

  1. Если тип (разрешение) не является объектом, тогда
    • Возврат FulfillPromise (обещание, разрешение).
  2. Пусть затем Get (разрешение, "then").
  3. Если это резкое завершение, то
    • Возврат RejectPromise (обещание, затем. [[значение]]).
  4. Пусть thenAction будет тогда. [[value]].
  5. Если IsCallable (thenAction) false, тогда
    • Возврат FulfillPromise (обещание, разрешение).
  6. Выполнить EnqueueJob ( "PromiseJobs", PromiseResolveThenableJob, "обещание, разрешение, затем действие" )

Ответ 2

Быстрое и грязное решение - проверить, имеет ли объект функцию then:

if (typeof message.then === 'function') {
    //assume it a Deferred or fits the Deferred interface
} else {
    //do other stuff
}