Як дізнатися, чи є об'єкт Обіцянкою?


336

Будь то обіцянка ES6 чи обіцянка синьої пташки, обіцянка Q тощо.

Як перевірити, чи є даний об'єкт Обіцянням?


3
У кращому випадку ви можете перевірити .thenметод, але це не скаже вам, що у вас є Обіцянка остаточно. Все, що ви знаєте на той момент, - це те, що у вас є щось, що викриває такий .thenметод, як Обіцянка.
Скотт Оффен

@ScottOffen специфікація обіцянки явно не робить різниці.
Бенджамін Груенбаум

6
Моя думка полягає в тому, що кожен може створити об’єкт, який викриває .thenметод, який не є Обіцянням, не поводиться як Обіцяння і не має наміру використовуватись як Обіцянка. Перевірка .thenметоду просто говорить вам, що якщо об'єкт не має .thenметоду, то у вас немає Обіцянки. Зворотне - що існування .thenметоду означає , що ви робите є обіцянка - це не завжди вірно.
Скотт Оффен

3
@ScottOffen За визначенням, єдиний встановлений спосіб визначити обіцянку - перевірити, чи є у неї .thenметод. Так, це може призвести до помилкових позитивів, але це припущення, на яке покладаються всі бібліотеки обіцянок (адже це все, на що вони можуть покластися). Наскільки я бачу, єдина альтернатива - прийняти пропозицію Бенджаміна Грюнбаума та провести її через набір тестів для обіцянок. Але це не практично для фактичного виробничого коду.
JLRishe

Відповіді:


342

Як вирішує бібліотека обіцянок

Якщо у нього є .thenфункція - це єдине стандартне використання бібліотек з обіцянками.

Специфікація Promises / A + має поняття, яке називається " thenздатне", яке в основному є "об'єктом з thenметодом". Обіцянки будуть і повинні засвоювати що завгодно тодішнім методом. Усі згадані вами обіцянки виконують це.

Якщо ми подивимось на специфікацію :

2.3.3.3, якщо thenце функція, викликайте її з x як це, перший аргумент разрешитьPromise, а другий аргумент відхилитиPromise

Він також пояснює обґрунтування цього дизайнерського рішення:

Ця обробка можливостей thenдозволяє реалізовувати обіцянки взаємодіяти, якщо вони виявляють thenметод, що відповідає Promises / A + -compliant . Це також дозволяє реалізаціям Promises / A + "асимілювати" невідповідні реалізації з розумними методами.

Як слід вирішити

Ви не повинні - натомість дзвонити Promise.resolve(x)( Q(x)у Q), який завжди перетворюватиме будь-яке значення чи зовнішнє, thenздатне в надійну обіцянку. Це безпечніше і простіше, ніж виконувати ці перевірки самостійно.

дійсно потрібно бути впевненим?

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


168

Перевірка, чи щось обіцяно, зайво ускладнює код, просто використовуйте Promise.resolve

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

})

1
тож Promise.resolve може впоратися з усім, що прийде на його шляху? Звичайно нічого, але я думаю, що щось розумне?
Олександр Міллс

3
@AlexMills так, це працює навіть для нестандартних обіцянок, таких як jQuery. Він може вийти з ладу, якщо в об'єкта є тодішній метод, який має зовсім інший інтерфейс від того, що обіцяє.
Есаїлія

19
Ця відповідь, хоч і може бути гарною порадою, насправді не відповідає на питання.
Штійн де Віт

4
Якщо питання справді не стосується того, хто фактично реалізує бібліотеку обіцянок, питання недійсне. Для перевірки знадобиться лише бібліотека обіцянок, після чого ви завжди можете використовувати її метод .resolve, як я показав.
Ісаїлія

4
@Esalija Питання здається мені актуальним і важливим, а не лише для виконавця бібліотеки з обіцянками. Це також стосується користувача бібліотеки з обіцянками, який хоче знати, як реалізовуватимуться / повинні / можуть поводитися програми та як різні бібліотеки обіцянок взаємодіють між собою. Зокрема, цього користувача сильно переживає очевидний факт, що я можу дати обіцянку X для будь-якого X, за винятком випадків, коли X означає "обіцянку" (що б там не було "обіцянка" - це питання), і мене, безумовно, цікавить точно знаючи, де лежать межі цього винятку.
Дон Хетч

103

Ось моя оригінальна відповідь, яка з тих пір була затверджена у специфікації як спосіб перевірити на обіцянку:

Promise.resolve(obj) == obj

Це працює тому, що алгоритм явно вимагає Promise.resolveповернення точного об'єкта, переданого в, якщо і тільки якщо це є обіцянкою за визначенням специфікації.

У мене є ще одна відповідь, яка раніше казала це, але я змінив його на щось інше, коли в той час він не працював із Safari. Це було рік тому, і це зараз надійно працює навіть у Safari.

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


10
ви повинні використовувати ===замість ==?
Ніл S

12
Це також не вдасться до обіцянок, які не є однаковою цариною.
Бенджамін Груенбаум

4
"обіцянка за визначенням специфікації", здається, означає "обіцянка, створена тим же конструктором, що і обіцянка, створена через Promise.resolve (), буде" - тому це не вдасться виявити, якщо, наприклад.
поліфазова Обіця

3
Цю відповідь можна було б покращити, якби вона почалася з того, що ви інтерпретуєте питання, а не починаєте з відповіді одразу - ОП, на жаль, зовсім не зрозуміло, і ви цього не зробили. ОП, письменник та читач, ймовірно, на 3 різних сторінках. Документ, на який ви посилаєтесь, говорить "якщо аргумент - це обіцянка , дана цим конструктором ", курсивна частина має вирішальне значення. Було б добре заявити, що це питання, на яке ви відповідаєте. Крім того, що ваша відповідь корисна для користувача цієї бібліотеки, але не для її реалізації.
Дон Хетч

1
Не використовуйте цей метод, ось чому, більш до точки @ BenjaminGruenbaum. gist.github.com/reggi/a1da4d0ea4f1320fa15405fb86358cff
ThomasReggi

61

Оновлення: це вже не найкраща відповідь. Будь ласка, проголосуйте замість моєї іншої відповіді .

obj instanceof Promise

повинен це зробити. Зауважте, що це може надійно працювати лише з нативними es6 обіцянками.

Якщо ви використовуєте шим, бібліотеку обіцянок або що-небудь інше, що претендує на подібність до обіцянки, то, можливо, буде більш доцільно протестувати на "Thenable" (що завгодно з .thenметодом), як показано в інших відповідях тут.


З тих пір мені було зазначено, що Promise.resolve(obj) == objв Сафарі не буде працювати. Використовуйте instanceof Promiseзамість цього.
липень

2
Це не працює надійно, і мені стало важко важко відстежувати проблему. Скажімо, у вас є бібліотека, яка використовує шрифт es6.promise, а ви десь використовуєте Bluebird, у вас виникнуть проблеми. Ця проблема виникла для мене в Chrome Canary.
воган

1
Так, ця відповідь насправді неправильна. Я опинився тут саме для такої важкої проблеми. Ви дійсно повинні obj && typeof obj.then == 'function'замість цього перевірити , тому що він буде працювати з усіма видами обіцянок і насправді є способом, рекомендованим специфікацією та використовуваним в реалізації / polyfills. Рідні, Promise.allнаприклад, будуть працювати над усіма можливостями 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?
Mörre

2
Це не вдасться обіцянкам, які не є однаковою цариною.
Бенджамін Груенбаум

46
if (typeof thing.then === 'function') {
    // probably a promise
} else {
    // definitely not a promise
}

6
що робити, якщо річ не визначена? вам потрібно
вберегтися

не найкраще, але, безумовно, дуже ймовірно; залежить також від масштабу проблеми. Писати 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]". Це може бути запланований результат або не залежно від проекту (наприклад, якщо є спеціальна реалізація обіцянок).


Щоб побачити, чи об’єкт з рідної 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 
}

Відповідно до цього та цього розділу специфікації, рядкове представлення функції має бути:

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

що обробляється відповідно вище. FunctionBody є[native code] у всіх основних браузерах.

MDN: Function.prototype.toString

Це працює також у різних середовищах.


12

Не відповідь на повне запитання, але я думаю, що варто згадати, що в Node.js 10 isPromiseбула додана нова утиліта, яка називається, яка перевіряє, чи об'єкт є власним Обіцянням чи ні:

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

utilTypes.isPromise(Promise.resolve(5)) // true
utilTypes.isPromise(b_Promise.resolve(5)) // false

11

Ось так, графікl-js пакет виявляє обіцянки:

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

value- повернене значення вашої функції. Я використовую цей код у своєму проекті і до цього часу не маю жодних проблем.


6

Ось форма коду https://github.com/ssnau/xkit/blob/master/util/is-promise.js

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

якщо об’єкт thenметодом, його слід трактувати як a Promise.


3
для чого нам потрібна умова = === "функція" btw?
Алендорф

Як і ця відповідь , будь-який об’єкт може мати метод "тоді", і тому не завжди може трактуватися як обіцянка.
Boghyon Hoffmann

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()
}

Якщо функція поверне обіцянку, вона буде чекати і повертатися з вирішеним значенням. Якщо функція поверне значення, вона буде розглядатися як вирішена.

Якщо функція не поверне обіцянку сьогодні, а завтра поверне одну або оголошена асинхронією, ви будете впевнені у майбутньому.


це працює, згідно з цим : "якщо значення [очікуваний] не є обіцянкою, [вираз очікування] перетворює значення на вирішене Обіцяння і чекає цього"
pqnet

Це в основному те, що було запропоновано у прийнятій відповіді, за винятком того, що тут використовується синтаксис, що очікує асинхрон,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));
});

2

Я використовую цю функцію як універсальне рішення:

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

-1

Після пошуку надійного способу виявлення функцій Async або навіть Обіцянь я закінчив, використовуючи наступний тест:

() => fn.constructor.name === 'Promise' || fn.constructor.name === 'AsyncFunction'

if you subclass Promise and create instances of that, this test can fail. this should work for most of what you're trying to test for though.
theram

Agreed, but I don’t see why would anyone create sublasses of promises
Sebastien H.

fn.constructor.name === 'AsyncFunction' is wrong - it means something is an async function and not a promise - also it is not guaranteed to work because people can subclass promises
Benjamin Gruenbaum

@BenjaminGruenbaum Наведений вище приклад працює в більшості випадків, якщо ви створюєте власний підклас, слід додати тести на його ім’я
Sebastien H.

Ви можете, але якщо ви вже знаєте, які об’єкти є, ви вже знаєте, чи є речі обіцянками чи ні.
Бенджамін Грюнбаум

-3

ES6:

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

console.log(promise.toString().includes('Promise')); //true

2
Будь-який об'єкт, який має (або перезаписав) toStringметод, може просто повернути рядок, що включає "Promise".
Boghyon Hoffmann

4
This answer is bad for many reasons, the most obvious being 'NotAPromise'.toString().includes('Promise') === true
damd
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.