Как мне узнать, является ли объект Обещанием?

336

Будь то ES6 Promise или Bluebird Promise, Q Promise и т. Д.

Как мне проверить, является ли данный объект Обещанием?

баран
источник
3
В лучшем случае вы можете проверить .thenметод, но это не скажет вам, что то, что у вас есть, является Обещанием окончательно. Все, что вы знаете в этот момент, - это то, что у вас есть что-то, что раскрывает .thenметод, например, Обещание.
Скотт Оффен
@ScottOffen спецификация обещания явно не делает различий.
Бенджамин Грюнбаум
6
Я .thenхочу сказать, что любой может создать объект, который предоставляет метод, который не является Обещанием, не ведет себя как Обещание и не имеет намерения использоваться как Обещание. Проверка на .thenметод просто говорит вам , что если объект не имеет .thenметод, то вы не имеете обещание. Обратное - что существование .thenметода означает , что вы делаете есть обещание - это не всегда верно.
Скотт Оффен
3
@ScottOffen По определению, единственный установленный способ идентифицировать обещание - проверить, есть ли у него .thenметод. Да, это может привести к ложным срабатываниям, но это предположение, что все библиотеки обещаний полагаются (потому что это все, на что они могут положиться). Насколько я вижу, единственная альтернатива - это принять предложение Бенджамина Грюнбаума и провести его через тестовый набор обещаний. Но это не практично для реального производственного кода.
JLRishe

Ответы:

342

Как решает библиотека обещаний

Если у него есть .thenфункция - это единственное стандартное обещание, которое используют библиотеки.

В спецификации Promises / A + есть понятие, называемое thenable, которое по сути является «объектом с thenметодом». Обещания будут и должны ассимилировать что-либо с помощью метода then. Все реализации обещаний, которые вы упомянули, делают это.

Если мы посмотрим на спецификации :

2.3.3.3 Если thenэто функция, вызовите ее с x как this, первый аргумент resolPromise и второй аргумент rejectPromise

Это также объясняет обоснование этого проектного решения:

Такое обращение с thenвозможностями позволяет взаимодействиям обещаний взаимодействовать, если они предоставляют thenспособ, соответствующий Promises / A + . Это также позволяет реализациям Promises / A + «ассимилировать» несовместимые реализации разумными методами then.

Как вы должны решить

Вы не должны - вместо этого вызывать Promise.resolve(x)( Q(x)в Q), который всегда преобразует любое значение или внешнее thenсостояние в доверенное обещание. Это безопаснее и проще, чем выполнять эти проверки самостоятельно.

действительно нужно быть уверенным?

Вы всегда можете запустить его через набор тестов : D

Бенджамин Грюнбаум
источник
168

Проверка того, что что-то обещает излишне усложняет код, просто используйте Promise.resolve

Promise.resolve(valueOrPromiseItDoesntMatter).then(function(value) {

})
Esailija
источник
1
так Promise.resolve может справиться с чем угодно ? Конечно, нет ничего, но я думаю, что-нибудь разумное?
Александр Миллс
3
@AlexMills да, это работает даже для нестандартных обещаний, таких как обещание jQuery. Он может потерпеть неудачу, если у объекта есть метод then, интерфейс которого полностью отличается от обещания then.
Esailija
19
Этот ответ, хотя, возможно, хороший совет, на самом деле не отвечает на вопрос.
Стейн де Витт
4
Если вопрос действительно не о том, кто-то на самом деле реализует библиотеку обещаний, вопрос недействителен. Только библиотека обещаний должна будет выполнить проверку, после этого вы всегда можете использовать ее метод .resolve, как я показал.
Esailija
4
@Esalija Вопрос представляется мне актуальным и важным, а не только для исполнителя библиотеки обещаний. Это также относится к пользователю библиотеки обещаний, который хочет знать, как будут / должны / могут вести себя реализации, и как разные библиотеки обещаний будут взаимодействовать друг с другом. В частности, этот пользователь сильно встревожен тем очевидным фактом, что я могу дать обещание X для любого X, кроме случаев, когда X означает «обещание» (что бы здесь ни значило «обещание» - вот в чем вопрос), и я определенно заинтересован точно знать, где лежат границы этого исключения.
Дон Хэтч
104

Вот мой оригинальный ответ, который с тех пор был утвержден в спецификации как способ проверки на обещание:

Promise.resolve(obj) == obj

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

У меня здесь есть другой ответ, который раньше говорил это, но я изменил его на что-то другое, когда он не работал с Safari в то время. Это было год назад, и теперь это надежно работает даже в Safari.

Я бы отредактировал свой первоначальный ответ, за исключением того, что он был ошибочным, учитывая, что к настоящему моменту за это решение проголосовало больше людей, чем за оригинал. Я считаю, что это лучший ответ, и я надеюсь, что вы согласны.

кливер
источник
10
следует использовать ===вместо ==?
Нил С
12
Это также не сработает для обещаний, которые не относятся к той же сфере.
Бенджамин Грюнбаум
4
«обещание по определению спецификации», по-видимому, означает «обещание, созданное тем же конструктором, что и обещание, созданное с помощью Promise.resolve ()» - так что это не удастся обнаружить, например, если. Обещание, заполненное полицией, на самом деле является Обещанием
VoxPelli
3
Этот ответ мог бы быть улучшен, если бы он начинался с указания того, как вы интерпретируете вопрос, а не сразу с ответа - ОП, к сожалению, вообще не прояснил его, и вы тоже этого не сделали, поэтому на данный момент ОП, писатель и читатель, скорее всего, находятся на 3 разных страницах. Документ, на который вы ссылаетесь, гласит: «Если аргумент является обещанием, созданным этим конструктором », выделенная курсивом часть является решающей. Было бы хорошо заявить, что на этот вопрос вы отвечаете. Также, что ваш ответ полезен для пользователя этой библиотеки, но не для разработчика.
Дон Хэтч
1
Не используйте этот метод, вот почему, больше в точку @ BenjaminGruenbaum. gist.github.com/reggi/a1da4d0ea4f1320fa15405fb86358cff
ThomasReggi
61

Обновление: это больше не лучший ответ. Пожалуйста, проголосуйте за мой другой ответ .

obj instanceof Promise

должен сделать это. Обратите внимание, что это может надежно работать только с собственными обещаниями es6.

Если вы используете прокладку, библиотеку обещаний или что-то еще, притворяющееся подобным обещанию, тогда может быть более уместно проверить «жизнеспособность» (что-нибудь с .thenметодом), как показано в других ответах здесь.

кливер
источник
С тех пор мне было указано, что не Promise.resolve(obj) == objбудет работать в Safari. Используйте instanceof Promiseвместо этого.
Джиб
2
Это не работает надежно и вызвало у меня невероятно сложную проблему. Скажем, у вас есть библиотека, которая использует прокладку es6.promise, и вы где-то используете Bluebird, у вас будут проблемы. Эта проблема возникла у меня в Chrome Canary.
Вон
1
Да, этот ответ на самом деле неправильный. Я попал сюда из-за такой сложной проблемы. Вы действительно должны проверить obj && typeof obj.then == 'function'вместо этого, потому что он будет работать со всеми типами обещаний и фактически является способом, рекомендованным спецификацией и используемым реализациями / полифиллами. Promise.allНапример, Native будет работать на всех thenспособностях, а не только на других обещаниях. Так должен ваш код. Так что instanceof Promiseэто не очень хорошее решение.
Стейн де Витт
2
Followup - это хуже: На node.js 6.2.2 , используя только собственные обещания , я сейчас пытаюсь отлаживать проблему , где console.log(typeof p, p, p instanceof Promise);производит этот выход: object Promise { <pending> } false. Как вы можете видеть, это обещание хорошо - и все же instanceof Promiseтест возвращается false?
Мёрре
2
Это не удастся для обещаний, которые не относятся к той же сфере.
Бенджамин Грюнбаум
46
if (typeof thing.then === 'function') {
    // probably a promise
} else {
    // definitely not a promise
}
unobf
источник
6
что если вещь не определена? вам нужно остерегаться этого с помощью вещи && ...
mrBorna
не самый лучший, но вполне вероятно; зависит также от масштаба проблемы. Написание 100% защиты обычно применимо в открытых открытых API-интерфейсах или в тех случаях, когда вы знаете, что форма / сигнатура данных полностью открыта.
rob2d
17

Чтобы увидеть, является ли данный объект Обещанием ES6 , мы можем использовать этот предикат:

function isPromise(p) {
  return p && Object.prototype.toString.call(p) === "[object Promise]";
}

CallING toStringнепосредственно от Object.prototypeвозвращения в родную строковое представление данного типа объекта , который "[object Promise]"в нашем случае. Это гарантирует, что данный объект

  • Обходит ложные срабатывания, такие как ..:
    • Определенный пользователем тип объекта с тем же именем конструктора («Обещание»).
    • Самописный toStringметод данного объекта.
  • Работает в разных контекстах среды (например, в фреймах) в отличие отinstanceof или isPrototypeOf.

Однако любой конкретный хост-объект , тег которого был изменен с помощьюSymbol.toStringTag , может вернуться "[object Promise]". Это может быть ожидаемый результат или нет в зависимости от проекта (например, если есть пользовательская реализация Promise).


Чтобы увидеть, является ли объект родным ES6 Promise , мы можем использовать:

function isNativePromise(p) {
  return p && typeof p.constructor === "function"
    && Function.prototype.toString.call(p.constructor).replace(/\(.*\)/, "()")
    === Function.prototype.toString.call(/*native object*/Function)
      .replace("Function", "Promise") // replacing Identifier
      .replace(/\(.*\)/, "()"); // removing possible FormalParameterList 
}

Согласно этому и этому разделу спецификации, строковое представление функции должно быть:

" Идентификатор функции ( FormalParameterList opt ) { FunctionBody }"

который обрабатывается соответственно выше. FunctionBody находится [native code]во всех основных браузерах.

MDN: Function.prototype.toString

Это работает в разных контекстах среды.

Богьон Хоффманн
источник
12

Не ответ на полный вопрос, но я думаю, что стоит упомянуть, что в Node.js 10 isPromiseбыла добавлена новая вызываемая функция, которая проверяет, является ли объект собственным Promise или нет:

const utilTypes = require('util').types
const b_Promise = require('bluebird')

utilTypes.isPromise(Promise.resolve(5)) // true
utilTypes.isPromise(b_Promise.resolve(5)) // false
LEQADA
источник
11

Вот как пакет graphql-js обнаруживает обещания:

function isPromise(value) {
  return Boolean(value && typeof value.then === 'function');
}

valueВозвращаемое значение вашей функции. Я использую этот код в своем проекте, и у меня пока нет проблем.

muratgozel
источник
6

Вот кодовая форма https://github.com/ssnau/xkit/blob/master/util/is-promise.js

!!obj && (typeof obj === 'object' || typeof obj === 'function') && typeof obj.then === 'function';

если объект с thenметодом, он должен рассматриваться как Promise.

ssnau
источник
3
зачем нам нужно obj === условие 'function'?
Алендорф
Как и в этом ответе , любой объект может иметь метод «затем» и, следовательно, не всегда может рассматриваться как обещание.
Богьон Хоффманн
6

Если вы используете Typescript , я хотел бы добавить, что вы можете использовать функцию «предиката типа». Просто следует обернуть логическую проверку в функцию, которая возвращает, x is Promise<any>и вам не нужно будет делать типы типов. Ниже на моем примере cприведено либо обещание, либо один из моих типов, которые я хочу преобразовать в обещание путем вызова c.fetch()метода.

export function toPromise(c: Container<any> | Promise<any>): Promise<any> {
    if (c == null) return Promise.resolve();
    return isContainer(c) ? c.fetch() : c;
}

export function isContainer(val: Container<any> | Promise<any>): val is Container<any> {
    return val && (<Container<any>>val).fetch !== undefined;
}

export function isPromise(val: Container<any> | Promise<any>): val is Promise<any> {
    return val && (<Promise<any>>val).then !== undefined;
}

Дополнительная информация: https://www.typescriptlang.org/docs/handbook/advanced-types.html.

Мурило Перроне
источник
6

Если вы используете асинхронный метод, вы можете сделать это и избежать двусмысленности.

async myMethod(promiseOrNot){
  const theValue = await promiseOrNot()
}

Если функция возвращает обещание, она будет ожидать и возвращаться с разрешенным значением. Если функция возвращает значение, оно будет считаться разрешенным.

Если функция не возвращает обещание сегодня, но завтра возвращает одно или объявляется как асинхронное, вы будете ориентированы на будущее.

Стивен Спунгин
источник
это работает, согласно здесь : «если [ожидало] значение не обещание, то [Await выражение] преобразует значение разрешенного Promise, и ждет его»
pqnet
Это в основном то, что было предложено в принятом ответе, за исключением того, что здесь используется синтаксис async-await вместоPromise.resolve()
B12Toaster
3
it('should return a promise', function() {
    var result = testedFunctionThatReturnsPromise();
    expect(result).toBeDefined();
    // 3 slightly different ways of verifying a promise
    expect(typeof result.then).toBe('function');
    expect(result instanceof Promise).toBe(true);
    expect(result).toBe(Promise.resolve(result));
});
purplecabbage
источник
2

Я использую эту функцию как универсальное решение:

function isPromise(value) {
  return value && value.then && typeof value.then === 'function';
}
safrazik
источник
-1

после поиска надежного способа обнаружения асинхронных функций или даже обещаний , я использовал следующий тест:

() => fn.constructor.name === 'Promise' || fn.constructor.name === 'AsyncFunction'
Себастьен Х.
источник
если вы Promiseсоздадите подкласс и создадите его экземпляры, этот тест может не пройти. это должно работать для большей части того, что вы пытаетесь проверить, хотя.
Терама
Согласен, но я не понимаю, зачем кому-то создавать подклассы обещаний
Себастьен Х.
fn.constructor.name === 'AsyncFunction'это неправильно - это означает, что что-то является асинхронной функцией, а не обещанием - также не гарантируется, что люди могут выполнять обещания подкласса
Бенджамин Грюнбаум
@BenjaminGruenbaum Приведенный выше пример работает в большинстве случаев, если вы создаете свой собственный подкласс, вы должны добавить тесты для его имени
Себастьен Х.
Вы можете, но если вы уже знаете, какие объекты есть, вы уже знаете, обещания или нет.
Бенджамин Грюнбаум
-3

ES6:

const promise = new Promise(resolve => resolve('olá'));

console.log(promise.toString().includes('Promise')); //true
Матиас Гено Аззолини
источник
2
Любой объект, который имеет (или перезаписал) toStringметод, может просто вернуть строку, которая включает "Promise".
Богьон Хоффманн
4
Этот ответ плох по многим причинам, наиболее очевидным из которых является'NotAPromise'.toString().includes('Promise') === true
damd