Bagaimana saya tahu jika suatu benda adalah Janji?

336

Apakah itu Janji ES6 atau Janji burung biru, Janji Q, dll.

Bagaimana cara saya menguji untuk melihat apakah objek yang diberikan adalah Janji?

theram
sumber
3
Paling-paling Anda bisa memeriksa suatu .thenmetode, tetapi itu tidak akan memberi tahu Anda apa yang Anda miliki adalah Janji yang pasti. Yang Anda tahu pada saat itu adalah bahwa Anda memiliki sesuatu yang memperlihatkan .thenmetode, seperti Janji.
Scott Offen
@ScottOffen spesifikasi janji secara eksplisit tidak membuat perbedaan.
Benjamin Gruenbaum
6
.thenMaksud saya adalah bahwa siapa pun dapat membuat objek yang memaparkan metode yang bukan Janji, tidak berperilaku seperti Janji dan tidak punya niat untuk digunakan seperti Janji. Memeriksa .thenmetode hanya memberi tahu Anda bahwa jika objek tidak memiliki .thenmetode, maka Anda tidak memiliki Janji. Terbalik - bahwa adanya .thenmetode berarti bahwa Anda lakukan memiliki Janji - belum tentu benar.
Scott Offen
3
@ScottOffen Menurut definisi, satu - satunya cara yang ditetapkan untuk mengidentifikasi janji adalah dengan memeriksa apakah ia memiliki .thenmetode. Ya, itu memiliki potensi positif palsu, tetapi asumsi bahwa semua perpustakaan janji mengandalkan (karena hanya itu yang bisa mereka andalkan). Satu-satunya alternatif sejauh yang saya lihat adalah mengambil saran Benjamin Gruenbaum dan menjalankannya melalui test suite janji. Tetapi itu tidak praktis untuk kode produksi aktual.
JLRishe

Jawaban:

342

Bagaimana perpustakaan janji memutuskan

Jika memiliki .thenfungsi - itulah satu - satunya perpustakaan janji standar yang digunakan.

Spesifikasi Janji / A + memiliki gagasan yang disebut thenmampu yang pada dasarnya adalah "objek dengan thenmetode". Janji akan dan harus mengasimilasi apa pun dengan metode saat itu. Semua implementasi janji yang Anda sebutkan melakukan ini.

Jika kita melihat spesifikasinya :

2.3.3.3 jika thensuatu fungsi, panggillah dengan x karena ini, argumen pertama resolPromise, dan argumen kedua rejectPromise

Ini juga menjelaskan alasan untuk keputusan desain ini:

Perlakuan yang thencakap ini memungkinkan implementasi janji untuk beroperasi, selama mereka memperlihatkan metode yang sesuai dengan Janji / A + then. Hal ini juga memungkinkan implementasi Janji / A + untuk "mengasimilasi" implementasi tidak sesuai dengan metode yang masuk akal.

Bagaimana Anda harus memutuskan

Anda seharusnya tidak - alih-alih menelepon Promise.resolve(x)( Q(x)dalam Q) yang akan selalu mengonversi nilai apa pun atau eksternal yang thendapat menjadi janji tepercaya. Ini lebih aman dan lebih mudah daripada melakukan pemeriksaan ini sendiri.

benar - benar harus yakin?

Anda selalu dapat menjalankannya melalui test suite : D

Benjamin Gruenbaum
sumber
168

Memeriksa apakah ada sesuatu yang dijanjikan tidak perlu mempersulit kode, cukup gunakan Promise.resolve

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

})
Esailija
sumber
1
jadi Promise.resolve dapat menangani apa pun yang datang padanya ? Tentunya bukan apa-apa, tapi saya kira sesuatu yang masuk akal?
Alexander Mills
3
@ AlexMills ya, itu bahkan berfungsi untuk janji-janji non standar seperti jQuery berjanji. Itu bisa gagal jika objek memiliki metode lalu yang memiliki antarmuka yang sama sekali berbeda dari yang dijanjikan.
Esailija
19
Jawaban ini, meskipun saran yang bagus, sebenarnya tidak menjawab pertanyaan.
Stijn de Witt
4
Kecuali pertanyaannya benar-benar tentang seseorang yang benar-benar menerapkan perpustakaan janji, pertanyaan itu tidak valid. Hanya perpustakaan janji yang perlu melakukan pemeriksaan, setelah itu Anda selalu dapat menggunakan metode .resolve-nya seperti yang saya tunjukkan.
Esailija
4
@Esalija Tampaknya bagi saya pertanyaan itu relevan dan penting, bukan hanya bagi pelaksana perpustakaan janji. Ini juga relevan untuk pengguna perpustakaan janji yang ingin tahu bagaimana implementasi akan / harus / mungkin berperilaku dan bagaimana perpustakaan janji yang berbeda akan berinteraksi satu sama lain. Secara khusus, pengguna ini sangat kecewa dengan kenyataan bahwa saya dapat membuat janji untuk X apa pun kecuali X adalah "janji" (apa pun "janji" artinya di sini-- itu pertanyaannya), dan saya pasti tertarik dalam mengetahui persis di mana batas-batas pengecualian itu berada.
Don Hatch
104

Inilah jawaban asli saya, yang sejak itu telah diratifikasi dalam spesifikasi sebagai cara untuk menguji janji:

Promise.resolve(obj) == obj

Ini berfungsi karena algoritma secara eksplisit menuntut bahwa Promise.resolveharus mengembalikan objek tepat yang dilewatkan jika dan hanya jika itu adalah janji dengan definisi spesifikasi.

Saya punya jawaban lain di sini, yang dulu mengatakan ini, tetapi saya mengubahnya ke sesuatu yang lain ketika tidak bekerja dengan Safari pada waktu itu. Itu setahun yang lalu, dan ini sekarang bekerja dengan baik bahkan di Safari.

Saya akan mengedit jawaban asli saya, kecuali yang terasa salah, mengingat bahwa lebih banyak orang sekarang telah memilih solusi yang diubah dalam jawaban itu daripada yang asli. Saya percaya ini adalah jawaban yang lebih baik, dan saya harap Anda setuju.

jib
sumber
10
yang harus Anda gunakan ===bukan ==?
Neil S
12
Ini juga akan gagal untuk janji-janji yang bukan dari bidang yang sama.
Benjamin Gruenbaum
4
"janji dengan definisi spec" tampaknya berarti "janji yang dibuat oleh konstruktor yang sama dengan janji yang dibuat melalui Promise.resolve () adalah" - jadi ini akan gagal mendeteksi jika mis. a Polyfilled Promise sebenarnya sebuah Janji
VoxPelli
3
Jawaban ini dapat ditingkatkan jika itu akan dimulai dengan menyatakan bagaimana Anda menafsirkan pertanyaan daripada memulai dengan jawaban segera - OP sayangnya tidak membuatnya sama sekali jelas, dan Anda belum melakukannya, jadi pada titik ini OP, penulis, dan pembaca kemungkinan ada di 3 halaman berbeda. Dokumen yang Anda rujuk mengatakan "jika argumennya adalah janji yang dihasilkan oleh konstruktor ini ", bagian yang dicetak miring menjadi sangat penting. Akan lebih baik untuk menyatakan bahwa itu adalah pertanyaan yang Anda jawab. Juga bahwa jawaban Anda bermanfaat untuk pengguna perpustakaan ini tetapi bukan implementornya.
Don Hatch
1
Jangan gunakan metode ini, inilah alasannya, lebih ke titik @ BenjaminGruenbaum. gist.github.com/reggi/a1da4d0ea4f1320fa15405fb86358cff
ThomasReggi
61

Pembaruan: Ini bukan lagi jawaban terbaik. Harap pilih jawaban saya yang lain sebagai gantinya.

obj instanceof Promise

harus melakukannya. Perhatikan bahwa ini hanya dapat bekerja dengan andal dengan janji ES6 asli.

Jika Anda menggunakan shim, perpustakaan janji, atau apa pun yang berpura-pura seperti janji, maka mungkin lebih tepat untuk menguji "layak" (apa pun dengan .thenmetode), seperti yang ditunjukkan dalam jawaban lain di sini.

jib
sumber
Sejak itu telah ditunjukkan kepada saya bahwa Promise.resolve(obj) == objtidak akan berfungsi di Safari. Gunakan instanceof Promisesebagai gantinya.
jib
2
Ini tidak bekerja dengan andal dan membuat saya kesulitan melacak masalah. Katakanlah Anda memiliki perpustakaan yang menggunakan shim es6.promise, dan Anda menggunakan Bluebird di suatu tempat, Anda akan memiliki masalah. Masalah ini muncul untuk saya di Chrome Canary.
vaughan
1
Ya, jawaban ini sebenarnya salah. Saya berakhir di sini untuk masalah yang sulit dilacak. Anda benar-benar harus memeriksa obj && typeof obj.then == 'function', karena itu akan bekerja dengan semua jenis janji dan sebenarnya cara yang direkomendasikan oleh spesifikasi dan digunakan oleh implementasi / polyfill. Native Promise.allmisalnya akan bekerja pada semua kemampuan then, tidak hanya janji asli lainnya. Begitu juga kode Anda. Jadi instanceof Promisebukan solusi yang baik.
Stijn de Witt
2
Tindak - itu lebih buruk: Pada Node.js 6.2.2 menggunakan hanya janji-janji asli saya sekarang mencoba untuk debug masalah di mana console.log(typeof p, p, p instanceof Promise);menghasilkan output ini: object Promise { <pending> } false. Seperti yang Anda lihat itu adalah janji baik-baik saja - namun instanceof Promisetes kembali false?
Mörre
2
Ini akan gagal untuk janji-janji yang bukan dari bidang yang sama.
Benjamin Gruenbaum
46
if (typeof thing.then === 'function') {
    // probably a promise
} else {
    // definitely not a promise
}
tidak ada
sumber
6
bagaimana jika sesuatu tidak terdefinisi? Anda harus waspada terhadap hal itu melalui && ...
mrBorna
bukan yang terbaik tetapi sangat mungkin; tergantung juga pada ruang lingkup masalah. Menulis 100% defensif biasanya berlaku di API publik terbuka atau di mana Anda tahu bentuk / tanda tangan data sepenuhnya terbuka.
rob2d
17

Untuk melihat apakah objek yang diberikan adalah Janji ES6 , kita dapat menggunakan predikat ini:

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

Calling toStringlangsung dari Object.prototypemengembalikan representasi string asli dari jenis objek yang diberikan "[object Promise]"dalam kasus kami. Ini memastikan objek yang diberikan

  • Bypasses false positive seperti ..:
    • Jenis objek yang didefinisikan sendiri dengan nama konstruktor yang sama ("Janji").
    • toStringMetode yang ditulis sendiri dari objek yang diberikan.
  • Bekerja di berbagai konteks lingkungan (mis. Iframe) berbeda denganinstanceof atau isPrototypeOf.

Namun, objek host tertentu apa pun , yang tagnya dimodifikasi melaluiSymbol.toStringTag , dapat kembali "[object Promise]". Ini mungkin hasil yang dimaksudkan atau tidak tergantung pada proyek (misalnya jika ada implementasi Janji kustom).


Untuk melihat apakah objek tersebut berasal dari Promise ES6 asli , kita dapat menggunakan:

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 
}

Menurut ini dan bagian ini dari spec, representasi string dari fungsi harus:

" Pengidentifikasi fungsi ( opt FormalParameterList ) { FunctionBody }"

yang ditangani sesuai di atas. The FunctionBody adalah [native code]di semua browser utama.

MDN: Function.prototype.toString

Ini berfungsi di berbagai konteks lingkungan juga.

Boghyon Hoffmann
sumber
12

Bukan jawaban untuk pertanyaan lengkap tapi saya pikir layak untuk menyebutkan bahwa di Node.js 10 isPromiseditambahkan fungsi util yang ditambahkan yang memeriksa apakah suatu objek adalah Janji asli atau tidak:

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

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

Beginilah cara paket graphql-js mendeteksi janji:

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

valueadalah nilai yang dikembalikan dari fungsi Anda. Saya menggunakan kode ini dalam proyek saya dan sejauh ini tidak ada masalah.

muratgozel
sumber
6

Ini formulir kode https://github.com/ssnau/xkit/blob/master/util/is-promise.js

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

jika suatu objek dengan suatu thenmetode, itu harus diperlakukan sebagai a Promise.

ssnau
sumber
3
mengapa kita membutuhkan kondisi obj === 'function' btw?
Alendorff
Sama seperti jawaban ini , objek apa pun dapat memiliki metode "lalu" dan dengan demikian tidak dapat selalu diperlakukan sebagai janji.
Boghyon Hoffmann
6

Jika Anda menggunakan TypeScript , saya ingin menambahkan bahwa Anda dapat menggunakan fitur "type predicate". Hanya harus membungkus verifikasi logis dalam fungsi yang mengembalikan x is Promise<any>dan Anda tidak perlu melakukan typecast. Di bawah ini pada contoh saya, cadalah janji atau salah satu dari tipe saya yang ingin saya ubah menjadi janji dengan memanggil c.fetch()metode.

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;
}

Info lebih lanjut: https://www.typescriptlang.org/docs/handbook/advanced-types.html

Murilo Perrone
sumber
6

Jika Anda menggunakan metode async, Anda dapat melakukan ini dan menghindari ambiguitas.

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

Jika fungsi mengembalikan janji, itu akan menunggu dan kembali dengan nilai yang diselesaikan. Jika fungsi mengembalikan nilai, itu akan diperlakukan sebagai diselesaikan.

Jika fungsi tidak mengembalikan janji hari ini, tetapi besok mengembalikan satu atau dinyatakan async, Anda akan menjadi bukti di masa depan.

Steven Spungin
sumber
ini bekerja, menurut sini : "jika nilai [yang ditunggu] bukan janji, [ekspresi yang menunggu] mengubah nilai menjadi Janji yang diselesaikan, dan menunggunya"
pqnet
Ini pada dasarnya adalah apa yang telah disarankan dalam jawaban yang diterima kecuali di sini sintaks async- Promise.resolve()
waiting
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
sumber
2

Saya menggunakan fungsi ini sebagai solusi universal:

function isPromise(value) {
  return value && value.then && typeof value.then === 'function';
}
safrazik
sumber
-1

setelah mencari cara yang dapat diandalkan untuk mendeteksi fungsi Async atau bahkan Janji , saya akhirnya menggunakan tes berikut:

() => fn.constructor.name === 'Promise' || fn.constructor.name === 'AsyncFunction'
Sebastien H.
sumber
jika Anda Promisemembuat subkelas dan membuat instance dari itu, tes ini bisa gagal. ini seharusnya bekerja untuk sebagian besar dari apa yang Anda coba untuk menguji.
theram
Setuju, tapi saya tidak mengerti mengapa ada orang yang membuat janji
Sebastien H.
fn.constructor.name === 'AsyncFunction'salah - itu berarti ada sesuatu yang merupakan fungsi async dan bukan janji - juga itu tidak dijamin untuk bekerja karena orang-orang dapat mensubclass janji
Benjamin Gruenbaum
@BenjaminGruenbaum Contoh di atas berfungsi dalam banyak kasus, jika Anda membuat subkelas Anda sendiri, Anda harus menambahkan tes pada namanya
Sebastien H.
Anda bisa, tetapi jika Anda sudah tahu benda apa yang ada, Anda sudah tahu apakah barangnya dijanjikan atau tidak.
Benjamin Gruenbaum
-3

ES6:

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

console.log(promise.toString().includes('Promise')); //true
Mathias Gheno Azzolini
sumber
2
Objek apa pun yang memiliki (atau telah ditimpa) toStringmetode hanya dapat mengembalikan string yang menyertakan "Promise".
Boghyon Hoffmann
4
Jawaban ini buruk karena banyak alasan, yang paling jelas'NotAPromise'.toString().includes('Promise') === true
damd