Bagaimana saya bisa secara sinkron menentukan status JavaScript Promise?

149

Saya memiliki Janji JavaScript murni (implementasi bawaan atau poli-isi):

var promise = new Promise(function (resolve, reject) { /* ... */ });

Dari spesifikasinya , Janji dapat menjadi salah satu dari:

  • 'diselesaikan' dan 'diselesaikan'
  • 'diselesaikan' dan 'ditolak'
  • 'tertunda'

Saya memiliki kasus penggunaan di mana saya ingin menginterogasi Janji secara serempak dan menentukan:

  • Apakah Janji sudah terpenuhi?

  • jika demikian, apakah Janji itu terselesaikan?

Saya tahu bahwa saya dapat menggunakan #then()untuk menjadwalkan pekerjaan yang akan dilakukan secara tidak sinkron setelah kondisi perubahan Janji. Saya TIDAK bertanya bagaimana melakukan ini.

Pertanyaan ini secara khusus tentang interogasi sinkron dari negara Janji . Bagaimana saya bisa mencapai ini?

Jokeyrhyme
sumber
6
atur properti pada janji yang bisa dilihat dari luar, dan gunakan then () untuk mengubah properti.
dandavis
@jokeyrhyme fwiw, v8 source code.google.com/p/v8/source/browse/branches/bleeding_edge/src/… lihat var promiseStatus = NEW_PRIVATE("Promise#status");, PromiseSetberfungsi diSET_PRIVATE(promise, promiseStatus, status);
guest271314
Ini dia: esdiscuss.org/topic/…
jokeyrhyme
Tampaknya aneh bahwa jika Anda melakukan const a = Promise.resolve ('baz'); console.log (a); dan lihat di konsol Chrome, Anda melihat Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: "baz"} proto: Promise [[PromiseStatus]]: "resolved" [[PromiseValue]]: "baz "Dan orang-orang mengklaim itu tidak bisa dilakukan. Bagaimana Chrome melakukannya? (melakukan ini di Plunker dengan Angular plnkr.co/edit/IPIWgLJKQStI5ubXmcsF
JGFMK
Menggunakan node v11.12.0 console.log akan menampilkan status janji. EG console.log(Promise.new((resolve, reject) => {})=>Promise { <pending> }
Puhlze

Jawaban:

77

Tidak ada API inspeksi sinkron seperti itu untuk janji JavaScript asli. Tidak mungkin melakukan ini dengan janji-janji asli. Spesifikasi tidak menentukan metode seperti itu.

Perpustakaan Userland dapat melakukan ini, dan jika Anda menargetkan mesin tertentu (seperti v8) dan memiliki akses ke kode platform (yaitu, Anda dapat menulis kode dalam inti ) maka Anda dapat menggunakan alat khusus (seperti simbol pribadi) untuk mencapai ini . Itu super spesifik dan bukan di userland.

Benjamin Gruenbaum
sumber
4
Catatan: Saya benar-benar yakin kasing kasus untuk inspeksi sinkron sedikit dan sangat jarang, jika Anda membagikan kasing kasus konkret Anda dalam pertanyaan baru yang menanyakan cara mencapainya tanpa inspeksi sinkron - saya akan menjawabnya jika ada orang yang tidak mau
habis
4
Bahkan jika kasus penggunaan jarang terjadi, apa salahnya termasuk sesuatu seperti ini? Saya perlu pemeriksaan status seperti ini untuk melihat apakah pekerjaan sebelumnya selesai dan apakah saya dapat meminta pekerjaan lain. Dan saya tidak bisa begitu saja menetapkan variabel eksternal karena objek memiliki potensi untuk mengubah pemilik tanpa pemberitahuan. Yang lebih menjengkelkan adalah saya bisa MELIHAT Node.js memiliki akses ke informasi ini karena itu menunjukkan kepada saya ketika saya memeriksanya, tetapi tidak ada cara untuk mendapatkannya selain string parsing ??
Tustin2121
9
Jadi kita harus membuang janji-janji asli karena tidak praktis dan selalu menggunakan bluebird. Kabar baik! Bagaimana saya mengusulkan janji-janji asli untuk menjadi usang dan dibuang dari mesin node?
user619271
1
Banyak hal, kita seharusnya berspekulasi .anydan membuat kesalahan karena Mark bersikeras. Untuk satu, Promise.race([])adalah janji yang tertunda selamanya (dan bukan kesalahan), Anda biasanya menginginkan janji sukses pertama dan bukan hanya janji pertama. Bagaimanapun, itu tidak benar-benar relevan dengan pertanyaan yang diajukan - OP bertanya tentang inspeksi sinkron dan bukan tentang .racedan banyak kekurangannya.
Benjamin Gruenbaum
5
@Akrikos jawaban itu tidak membiarkan Anda memeriksa kondisi janji secara sinkron - Misalnya MakeQueryablePromise(Promise.resolve(3)).isResolvedsalah tetapi janji itu jelas-jelas diselesaikan. Belum lagi jawaban itu juga menggunakan istilah "diselesaikan" dan "terpenuhi" salah. Untuk melakukan itu, Anda dapat menambahkan .thenhandler sendiri - yang benar-benar melenceng dari titik pemeriksaan sinkron.
Benjamin Gruenbaum
31

masukkan deskripsi gambar di sini

janji-status-async berhasil . Itu async tetapi tidak digunakan thenuntuk menunggu janji untuk diselesaikan.

const {promiseStatus} = require('promise-status-async');
// ...
if (await promiseStatus(promise) === 'pending') {
    const idle = new Promise(function(resolve) {
        // can do some IDLE job meanwhile
    });
    return idle;
}
0xaB
sumber
4
OP bertanya tentang bagaimana melakukannya secara sinkron
Klesun
28

Tidak, tidak ada API sinkronisasi, tapi ini versi async saya promiseState(dengan bantuan dari @Matthijs):

function promiseState(p) {
  const t = {};
  return Promise.race([p, t])
    .then(v => (v === t)? "pending" : "fulfilled", () => "rejected");
}

var a = Promise.resolve();
var b = Promise.reject();
var c = new Promise(() => {});

promiseState(a).then(state => console.log(state)); // fulfilled
promiseState(b).then(state => console.log(state)); // rejected
promiseState(c).then(state => console.log(state)); // pending

jib
sumber
Apakah ada alasan khusus di balik konstruksi ini? Sepertinya tidak perlu rumit bagi saya. Sejauh yang saya tahu ini bekerja secara identik: Promise.race([ Promise.resolve(p).then(() => "fulfilled", () => "rejected"), Promise.resolve().then(() => "pending") ]); Meskipun ini tampaknya lebih aman bagi saya: const t = {}; return Promise.race([p,t]).then(v => v === t ? "pending" : "fulfilled", () => "rejected") dan menghindari membuat janji-janji tambahan yang bertahan selama p yang asli tertunda.
Matthijs
Terima kasih @Matthijs! Saya telah menyederhanakan jawaban saya.
jib
16

Anda dapat membuat balapan dengan Promise.resolve
Ini tidak sinkron tetapi terjadi sekarang

function promiseState(p, isPending, isResolved, isRejected) {
  Promise.race([p, Promise.resolve('a value that p should not return')]).then(function(value) {
    if (value == 'a value that p should not return') {
      (typeof(isPending) === 'function') && isPending();
    }else {
      (typeof(isResolved) === 'function') && isResolved(value);
    }
  }, function(reason) {
    (typeof(isRejected) === 'function') && isRejected(reason);
  });
}

Sebuah skrip kecil untuk menguji dan memahami maknanya secara asinkron

var startTime = Date.now() - 100000;//padding trick "100001".slice(1) => 00001
function log(msg) {
  console.log((""+(Date.now() - startTime)).slice(1) + ' ' + msg);
  return msg;//for chaining promises
};

function prefix(pref) { return function (value) { log(pref + value); return value; };}

function delay(ms) {
  return function (value) {
    var startTime = Date.now();
    while(Date.now() - startTime < ms) {}
    return value;//for chaining promises
  };
}
setTimeout(log, 0,'timeOut 0 ms');
setTimeout(log, 100,'timeOut 100 ms');
setTimeout(log, 200,'timeOut 200 ms');

var p1 = Promise.resolve('One');
var p2 = new Promise(function(resolve, reject) { setTimeout(resolve, 100, "Two"); });
var p3 = Promise.reject("Three");

p3.catch(delay(200)).then(delay(100)).then(prefix('delayed L3 : '));

promiseState(p1, prefix('p1 Is Pending '), prefix('p1 Is Resolved '), prefix('p1 Is Rejected '));
promiseState(p2, prefix('p2 Is Pending '), prefix('p2 Is Resolved '), prefix('p2 Is Rejected '));
promiseState(p3, prefix('p3 Is Pending '), prefix('p3 Is Resolved '), prefix('p3 Is Rejected '));

p1.then(prefix('Level 1 : ')).then(prefix('Level 2 : ')).then(prefix('Level 3 : '));
p2.then(prefix('Level 1 : ')).then(prefix('Level 2 : ')).then(prefix('Level 3 : '));
p3.catch(prefix('Level 1 : ')).then(prefix('Level 2 : ')).then(prefix('Level 3 : '));
log('end of promises');
delay(100)();
log('end of script');

hasil dengan penundaan (0) (beri komentar pada saat penundaan)

00001 end of promises
00001 end of script
00001 Level 1 : One
00001 Level 1 : Three
00001 p1 Is Resolved One
00001 p2 Is Pending undefined
00001 p3 Is Rejected Three
00001 Level 2 : One
00001 Level 2 : Three
00001 delayed L3 : Three
00002 Level 3 : One
00002 Level 3 : Three
00006 timeOut 0 ms
00100 timeOut 100 ms
00100 Level 1 : Two
00100 Level 2 : Two
00101 Level 3 : Two
00189 timeOut 200 ms

dan hasil tes ini dengan firefox (chrome keep the order)

00000 end of promises
00100 end of script
00300 Level 1 : One
00300 Level 1 : Three
00400 p1 Is Resolved One
00400 p2 Is Pending undefined
00400 p3 Is Rejected Three
00400 Level 2 : One
00400 Level 2 : Three
00400 delayed L3 : Three
00400 Level 3 : One
00400 Level 3 : Three
00406 timeOut 0 ms
00406 timeOut 100 ms
00406 timeOut 200 ms
00406 Level 1 : Two
00407 Level 2 : Two
00407 Level 3 : Two

janjiState membuat .race dan .then: Level 2

Steween
sumber
3
Alih-alih 'a value that p should not return', gunakan Simbol
programmer5000
1
@ programmer5000 Apa manfaatnya?
Moritz Schmitz v. Hülst
2
@ MoritzSchmitzv. Pertama a Symbolakan menjadi nilai unik, oleh karena itu Anda tidak perlu menebak "nilai [...] apa yang tidak boleh dikembalikan." Namun, referensi ke objek tertentu akan berfungsi juga.
Scott Rudiger
7

Anda dapat menggunakan peretasan (jelek) di Node.js hingga metode asli ditawarkan:

util = require('util');

var promise1 = new Promise (function (resolve) {
}

var promise2 = new Promise (function (resolve) {

    resolve ('foo');
}

state1 = util.inspect (promise1);
state2 = util.inspect (promise2);

if (state1 === 'Promise { <pending> }') {

    console.log('pending'); // pending
}

if (state2 === "Promise { 'foo' }") {

    console.log ('foo') // foo
}
rabbitco
sumber
3
Saya sudah merebusnya menjadi polyfill:Promise.prototype.isPending = function(){ return util.inspect(this).indexOf("<pending>")>-1; }
Tustin2121
5
Itu menghebohkan .
John Weisz
@JohnWeisz Yang paling mengerikan adalah kurangnya kompatibilitas. Saya mencoba untuk mengintegrasikan API janji-ful ke basis kode yang menganggap semuanya sinkron. Entah itu melakukan sesuatu yang menghebohkan atau menulis ulang sejumlah besar kode. Bagaimanapun aku melakukan kekejaman.
rath
4
cukup gunakanprocess.binding('util').getPromiseDetails
amara
@ Tustin2121 Untuk beberapa versi akan gagal dengan sesuatu seperti Promise.resolve('<pending>').
user202729
7

dalam simpul, katakanlah internal tidak berdokumen process.binding('util').getPromiseDetails(promise)

> process.binding('util').getPromiseDetails(Promise.resolve({data: [1,2,3]}));
[ 1, { data: [ 1, 2, 3 ] } ]

> process.binding('util').getPromiseDetails(Promise.reject(new Error('no')));
[ 2, Error: no ]

> process.binding('util').getPromiseDetails(new Promise((resolve) => {}));
[ 0, <1 empty item> ]
amara
sumber
saya menambahkan ini karena tidak ada di salah satu jawaban yang ada, dan untuk simpul itu adalah jawaban terbaik. mudah untuk mencari dokumen di github.com/nodejs/node
amara
6

Diperbarui: 2019

Bluebird.js menawarkan ini: http://bluebirdjs.com/docs/api/isfulfilled.html

var Promise = require("bluebird");
let p = Promise.resolve();
console.log(p.isFulfilled());

Jika Anda lebih suka membuat bungkus Anda sendiri, berikut adalah blog yang bagus .

Karena JavaScript adalah single-threaded, sulit untuk menemukan kasus penggunaan yang cukup umum untuk membenarkan memasukkan ini ke dalam spesifikasi. Tempat terbaik untuk mengetahui apakah janji terselesaikan ada di .then (). Menguji apakah Janji telah dipenuhi akan membuat sebuah polling loop yang kemungkinan besar adalah arah yang salah.

async / await adalah konstruk yang bagus jika Anda ingin memberikan kode async secara sinkron.

await this();
await that();
return 'success!';

Panggilan berguna lainnya adalah Promise.all ()

var promise1 = Promise.resolve(3);
var promise2 = 42;
var promise3 = new Promise(function(resolve, reject) {
  setTimeout(resolve, 100, 'foo');
});

Promise.all([promise1, promise2, promise3]).then(function(values) {
  console.log(values);
});
// expected output: Array [3, 42, "foo"]

Ketika saya pertama kali meraih jawaban ini, itulah use case yang saya cari.

Michael Cole
sumber
5

Anda dapat memenuhi janji Anda dengan cara ini

function wrapPromise(promise) {
  var value, error,
      settled = false,
      resolved = false,
      rejected = false,
      p = promise.then(function(v) {
        value = v;
        settled = true;
        resolved = true;
        return v;
      }, function(err) {
        error = err;
        settled = true;
        rejected = true;
        throw err;
      });
      p.isSettled = function() {
        return settled;
      };
      p.isResolved = function() {
        return resolved;
      };
      p.isRejected = function() {
        return rejected;
      };
      p.value = function() {
        return value;
      };
      p.error = function() {
        return error;
      };
      var pThen = p.then, pCatch = p.catch;
      p.then = function(res, rej) {
        return wrapPromise(pThen(res, rej));
      };
      p.catch = function(rej) {
        return wrapPromise(pCatch(rej));
      };
      return p;
}
SpiderPig
sumber
5
Ini akan membutuhkan OP untuk mendapatkan akses ke janji di giliran sebelumnya dari loop acara . Karena .thenselalu mengeksekusi OP tidak sinkron yang ingin memeriksa janji pada gilirannya yang sama tidak akan mendapatkan hasil yang benar di sini. Catatan OP bertanya secara spesifik tentang inspeksi sinkron dan menyebutkan bahwa mereka sudah tahu tentang inspeksi asinkron.
Benjamin Gruenbaum
@BenjaminGruenbaum: bukankah nilai default akan muncul jika kode pada "turn" yang sama menyebutnya?
dandavis
Tentu saja Anda harus membungkus semua janji Anda pada saat penciptaan. misalnya di dalam fungsi yang membuat dan mengembalikannya.
SpiderPig
3
Benar, pada titik mana mereka sebenarnya bukan janji asli lagi, Anda mungkin juga memperpanjangnya dengan cara mereka seharusnya diperluas dengan subklas yang akan memungkinkan Anda untuk melakukan ini dengan elegan alih-alih properti menambal monyet pada objek.
Benjamin Gruenbaum
Apakah Anda memberikan janji seperti yang saya tunjukkan atau dengan subklasifikasi, dalam setiap kasus Anda masih harus menambahkan versi Anda sendiri saat itu dan menangkapnya.
SpiderPig
5

Memang sangat menjengkelkan bahwa fungsi dasar ini tidak ada. Jika Anda menggunakan node.js maka saya tahu dua solusi, tak satu pun dari mereka sangat cantik. Kedua cuplikan di bawah ini menerapkan API yang sama:

> Promise.getInfo( 42 )                         // not a promise
{ status: 'fulfilled', value: 42 }
> Promise.getInfo( Promise.resolve(42) )        // fulfilled
{ status: 'fulfilled', value: 42 }
> Promise.getInfo( Promise.reject(42) )         // rejected
{ status: 'rejected', value: 42 }
> Promise.getInfo( p = new Promise(() => {}) )  // unresolved
{ status: 'pending' }
> Promise.getInfo( Promise.resolve(p) )         // resolved but pending
{ status: 'pending' }

Sepertinya tidak ada cara untuk membedakan kedua negara janji terakhir menggunakan salah satu trik.

1. Gunakan API debug V8

Ini adalah trik yang sama yang util.inspectdigunakan.

const Debug = require('vm').runInDebugContext('Debug');

Promise.getInfo = function( arg ) {
    let mirror = Debug.MakeMirror( arg, true );
    if( ! mirror.isPromise() )
        return { status: 'fulfilled', value: arg };
    let status = mirror.status();
    if( status === 'pending' )
        return { status };
    if( status === 'resolved' )  // fix terminology fuck-up
        status = 'fulfilled';
    let value = mirror.promiseValue().value();
    return { status, value };
};

2. Jalankan microtasks secara sinkron

Ini menghindari API debug, tetapi memiliki semantik yang menakutkan dengan menyebabkan semua mikrotasks dan process.nextTickcallback yang tertunda dijalankan secara serempak. Ini juga memiliki efek samping mencegah kesalahan "penolakan janji yang tidak tertangani" pernah dipicu untuk janji yang diinspeksi.

Promise.getInfo = function( arg ) {
    const pending = {};
    let status, value;
    Promise.race([ arg, pending ]).then(
        x => { status = 'fulfilled'; value = x; },
        x => { status = 'rejected'; value = x; }
    );
    process._tickCallback();  // run microtasks right now
    if( value === pending )
        return { status: 'pending' };
    return { status, value };
};
Matthijs
sumber
Sangat tidak aman untuk melakukan process._tickCallback(atau bahkan% RunMicrotick) - ini akan secara acak merusak hal-hal dalam kode Anda. Saya mati-matian mencoba membuatnya bekerja (untuk timer palsu dalam fungsi async, kebanyakan) dan itu tidak pernah cukup stabil dari sisi Node. Saya agak menyerah mengerjakannya. API mirror debug V8 sepenuhnya sesuai di sini.
Benjamin Gruenbaum
Dan .. DeprecationWarning: DebugContext has been deprecated and will be removed in a future version.:( Sepertinya V8 menghapusnya
Benjamin Gruenbaum
Kami (Node) benar-benar dapat meminta V8 untuk API atau mengekspos API untuk melihat keadaan janji secara langsung - jika Anda membuka masalah di github.com/nodejs/promise-use-cases saya akan membawanya dengan V8 dengan senang hati
Benjamin Gruenbaum
1
Komentar lebih lanjut dalam topik ini mengungkapkan bahwa API sudah ada: process.binding('util').getPromiseDetails( promise )pengembalian [ 0, ]untuk menunggu, [ 1, value ]untuk dipenuhi, dan [ 2, value ]untuk ditolak.
Matthijs
3

Peringatan: Metode ini menggunakan internal Node.js yang tidak berdokumen dan dapat diubah tanpa peringatan.

Di Node, Anda dapat secara sinkron menentukan status janji menggunakan process.binding('util').getPromiseDetails(/* promise */);.

Ini akan mengembalikan:

[0, ] untuk menunggu,

[1, /* value */] untuk dipenuhi, atau

[2, /* value */] untuk ditolak.

const pending = new Promise(resolve => setTimeout(() => resolve('yakko')));;
const fulfilled = Promise.resolve('wakko');
const rejected = Promise.reject('dot');

[pending, fulfilled, rejected].forEach(promise => {
  console.log(process.binding('util').getPromiseDetails(promise));
});

// pending:   [0, ]
// fulfilled: [1, 'wakko']
// rejected:  [2, 'dot']

Membungkus ini ke dalam fungsi pembantu:

const getStatus = promise => ['pending', 'fulfilled', 'rejected'][
  process.binding('util').getPromiseDetails(promise)[0]
];

getStatus(pending); // pending
getStatus(fulfilled); // fulfilled
getStatus(rejected); // rejected
Scott Rudiger
sumber
Sepertinya tidak bekerja dari dalam jest(yang merupakan satu-satunya tempat saya tertarik, sungguh). Fungsi itu ada, tetapi sepertinya selalu kembali undefined. Bagaimana saya mencari tahu apa yang salah?
Adam Barnes
Hmm, saya ingat itu bekerja di dalam mocha; tidak pernah mencobanya dengan jestsekalipun. Mungkin memulai pertanyaan baru yang menghubungkan di sini dan termasuk versi Node.js Anda dan juga jestversi?
Scott Rudiger
Bukan sesuatu yang saya minati lagi, sayangnya. Saya pada dasarnya mencari untuk menguji kewarasan saya secara manual-diselesaikan / ditolak Promisebahwa saya hanya menggunakan untuk menguji hal-hal yang harus terjadi ketika Promisesedang tertunda, tapi saya pikir selama apa yang saya tulis, bekerja, maka tidak perlu untuk menguji bahwa selain apa yang bergantung padanya.
Adam Barnes
2

apa yang dapat Anda lakukan, adalah menggunakan variabel untuk menyimpan keadaan, secara manual mengatur keadaan ke variabel itu, dan memeriksa variabel itu.

var state = 'pending';

new Promise(function(ff, rjc) {
  //do something async

  if () {//if success
    state = 'resolved';

    ff();//
  } else {
    state = 'rejected';

    rjc();
  }
});

console.log(state);//check the state somewhere else in the code

tentu saja, ini berarti Anda harus memiliki akses ke kode janji yang asli. Jika tidak, maka Anda dapat melakukan:

var state = 'pending';

//you can't access somePromise's code
somePromise.then(function(){
  state = 'resolved';
}, function() {
  state = 'rejected';
})

console.log(state);//check the promise's state somewhere else in the code

Solusi saya lebih banyak coding, tapi saya pikir Anda mungkin tidak perlu melakukan ini untuk setiap janji yang Anda gunakan.

Nama Besar
sumber
2

Pada Node.js versi 8, Anda sekarang dapat menggunakan paket inspeksi bijak untuk secara sinkron memeriksa janji-janji asli (tanpa peretasan berbahaya).

Joshua Wise
sumber
2

Anda dapat menambahkan metode ke Promise.prototype. Ini terlihat seperti ini:

Diedit: Solusi pertama tidak berfungsi sebagaimana mestinya, seperti sebagian besar jawaban di sini. Ini mengembalikan "pending" sampai fungsi asinkron ".then" dipanggil, yang tidak terjadi segera. (Hal yang sama adalah tentang solusi menggunakan Promise.race). Solusi kedua saya memecahkan masalah ini.

if (window.Promise) {
    Promise.prototype.getState = function () {
        if (!this.state) {
            this.state = "pending";
            var that = this;
            this.then(
                function (v) {
                    that.state = "resolved";
                    return v;
                },
                function (e) {
                    that.state = "rejected";
                    return e;
                });
        }
        return this.state;
    };
}

Anda dapat menggunakannya di Janji apa pun. Sebagai contoh:

myPromise = new Promise(myFunction);
console.log(myPromise.getState()); // pending|resolved|rejected

Solusi kedua (dan benar):

if (window.Promise) {
    Promise.stateable = function (func) {
        var state = "pending";
        var pending = true;
        var newPromise = new Promise(wrapper);
        newPromise.state = state;
        return newPromise;
        function wrapper(resolve, reject) {
            func(res, rej);
            function res(e) {
                resolve(e);
                if (pending) {
                    if (newPromise)
                        newPromise.state = "resolved";
                    else
                        state = "resolved";
                    pending = false;
                }
            }
            function rej(e) {
                reject(e);
                if (pending) {
                    if (newPromise)
                        newPromise.state = "rejected";
                    else
                        state = "rejected";
                    pending = false;
                }
            }
        }
    };
}

Dan gunakan itu:

Perhatikan : Dalam solusi ini Anda tidak harus menggunakan operator "baru".

myPromise = Promise.stateable(myFunction);
console.log(myPromise.state); // pending|resolved|rejected
Moshe
sumber
1

Ini adalah versi QueryablePromise yang lebih sempurna, memungkinkan kemampuan untuk rantai kemudian dan menangkap setelah tekad pertama dan segera menyelesaikan atau menolak untuk menjaga api tetap konsisten dengan Janji asli.

const PROMISE = Symbol('PROMISE')
const tap = fn => x => (fn(x), x)
const trace = label => tap(x => console.log(label, x))

class QueryablePromise {
  resolved = false
  rejected = false
  fulfilled = false
  catchFns = []
  constructor(fn) {
    this[PROMISE] = new Promise(fn)
      .then(tap(() => {
        this.fulfilled = true
        this.resolved = true
      }))
      .catch(x => {
        this.fulfilled = true
        this.rejected = true
        return Promise.reject(x)
      })
  }
  then(fn) {
    this[PROMISE].then(fn)
    return this
  }
  catch(fn) {
    this[PROMISE].catch(fn)
    return this
  }
  static resolve(x) {
    return new QueryablePromise((res) => res(x))
  }
  static reject(x) {
    return new QueryablePromise((_, rej) => rej(x))
  }
}

const resolvedPromise = new QueryablePromise((res) => {
  setTimeout(res, 200, 'resolvedPromise')
})

const rejectedPromise = new QueryablePromise((_, rej) => {
  setTimeout(rej, 200, 'rejectedPromise')
})

// ensure our promises have not been fulfilled
console.log('test 1 before: is resolved', resolvedPromise.resolved)
console.log('test 2 before: is rejected', rejectedPromise.rejected)


setTimeout(() => {
  // check to see the resolved status of our promise
  console.log('test 1 after: is resolved', resolvedPromise.resolved)
  console.log('test 2 after: is rejected', rejectedPromise.rejected)
}, 300)

// make sure we can immediately resolve a QueryablePromise
const immediatelyResolvedPromise = QueryablePromise.resolve('immediatelyResolvedPromise')
  // ensure we can chain then
  .then(trace('test 3 resolved'))
  .then(trace('test 3 resolved 2'))
  .catch(trace('test 3 rejected'))

// make sure we can immediately reject a QueryablePromise
const immediatelyRejectedPromise = QueryablePromise.reject('immediatelyRejectedPromise')
  .then(trace('test 4 resolved'))
  .catch(trace('test 4 rejected'))
<script src="https://codepen.io/synthet1c/pen/KyQQmL.js"></script>

synthet1c
sumber
1

awaitpenggunaan untuk jawaban @ jib , dengan prototyping idiomatik.

Object.defineProperty(Promise.prototype, "state", {
    get: function(){
        const o = {};
        return Promise.race([this, o]).then(
            v => v === o ? "pending" : "resolved",
            () => "rejected");
    }
});

// usage: console.log(await <Your Promise>.state);
(async () => {
    console.log(await Promise.resolve(2).state);  // "resolved"
    console.log(await Promise.reject(0).state);   // "rejected"
    console.log(await new Promise(()=>{}).state); // "pending"
})();

perhatikan bahwa fungsi async ini menjalankan "hampir" segera seperti fungsi yang disinkronkan (atau sebenarnya mungkin secara instan).

Valen
sumber
1

2019:

Cara sederhana untuk melakukan itu seperti yang saya tahu adalah thenable, pembungkus super tipis di sekitar janji atau pekerjaan async.

const sleep = (t) => new Promise(res => setTimeout(res,t));
const sleeping = sleep(30);

function track(promise){
    let state = 'pending';
    promise = promise.finally( _=> state ='fulfilled');
    return {
        get state(){return state},
        then: promise.then.bind(promise), /*thentable*/
        finally:promise.finally.bind(promise),
        catch:promise.catch.bind(promise),
    }
}


promise = track(sleeping);
console.log(promise.state) // pending

promise.then(function(){
    console.log(promise.state); // fulfilled
})
pery mimon
sumber
1

Anda dapat extendkelas Promise untuk membuat kelas Promise yang dapat ditanyakan baru .

Anda dapat membuat subclass Anda sendiri, katakanlah QueryablePromise, dengan mewarisi dari Promisekelas yang tersedia secara asli , instance yang akan memiliki statusproperti yang tersedia di dalamnya yang dapat Anda gunakan untuk menanyakan status objek janji secara serempak . Implementasinya dapat dilihat di bawah atau merujuk ini untuk penjelasan yang lebih baik.

class QueryablePromise extends Promise {
  constructor (executor) {
    super((resolve, reject) => executor(
      data => {
        resolve(data)
        this._status = 'Resolved'
      },
      err => {
        reject(err)
        this._status = 'Rejected'
      },
    ))
    this._status = 'Pending'
  }

  get status () {
    return this._status
  }
}
 
// Create a promise that resolves after 5 sec 
var myQueryablePromise = new QueryablePromise((resolve, reject) => {
  setTimeout(() => resolve(), 5000)
})

// Log the status of the above promise every 500ms
setInterval(() => {
  console.log(myQueryablePromise.status)
}, 500)

UtkarshPramodGupta
sumber
Sayangnya, tidak ada API yang ada akan mengembalikan kelas baru ini. Bagaimana Anda membayangkan orang menggunakan ini?
jib
@ jib Terima kasih atas tanggapan Anda. Apa maksud Anda bahwa tidak ada API yang akan mengembalikan kelas ini? :(
UtkarshPramodGupta
Tidak ada API yang ada akan mengembalikannya, karena mereka harus ditulis untuk mengembalikannya, kan? Misal jika saya menyebutnya fetchakan mengembalikan janji asli. Bagaimana kelas Anda membantu hal itu?
jib
Nah, kita tidak bisa hanya membungkus bahwa mengambil panggilan di QuerablePromise baru seperti: const queryableFetch = new QueryablePromise((resolve, reject) => {fetch(/.../).then((data) => resolve(data)) })? Atau, apakah ada masalah dengan itu? : /
UtkarshPramodGupta
Itu harus bekerja, jangan lupa , err => reject(err)sebagai argumen kedua thenatau tidak akan menyebarkan kesalahan dengan benar (di antara alasan itu dianggap sebagai konstruktor janji anti-pola ). Itu tidak benar-benar sinkron (misalnya tidak akan mendeteksi janji yang sudah diselesaikan), tetapi mungkin berguna dalam kasus di mana Anda tidak mengontrol penelepon dan jawabannya diperlukan segera.
jib
1

Ada lagi yang elegan cara & Hacky memeriksa apakah janji masih tertunda hanya dengan mengubah seluruh objek string dan memeriksa dengan bantuan memeriksa seperti ini: util.inspect(myPromise).includes("pending").

Diuji pada Node.js 8,9,10,11,12,13

Ini contoh lengkapnya

const util = require("util")

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

(async ()=>{
  let letmesleep = sleep(3000)
  setInterval(()=>{
    console.log(util.inspect(letmesleep).includes("pending"))
  },1000)
})()

Hasil:

true
true
false
false
false
devio
sumber
0

Jika Anda menggunakan ES7 eksperimental, Anda dapat menggunakan async untuk dengan mudah membungkus janji yang ingin Anda dengarkan.

async function getClient() {
  let client, resolved = false;
  try {
    client = await new Promise((resolve, reject) => {
      let client = new Client();

      let timer = setTimeout(() => {
         reject(new Error(`timeout`, 1000));
         client.close();
      });

      client.on('ready', () => {
        if(!resolved) {
          clearTimeout(timer);
          resolve(client);
        }
      });

      client.on('error', (error) => {
        if(!resolved) {
          clearTimeout(timer);
          reject(error);
        }
      });

      client.on('close', (hadError) => {
        if(!resolved && !hadError) {
          clearTimeout(timer);
          reject(new Error("close"));
        }
      });
    });

    resolved = true;
  } catch(error) {
    resolved = true;
    throw error;
  }
  return client;
}
Ezequiel S. Pereira
sumber
0

Saya telah menulis paket npm kecil, nilai janji, yang menyediakan pembungkus janji dengan resolvedbendera:

https://www.npmjs.com/package/promise-value

Ini juga memberikan akses sinkron ke nilai janji (atau kesalahan). Ini tidak mengubah objek Janji itu sendiri, mengikuti bungkus daripada memperpanjang pola.

Daniel Winterstein
sumber
0

Ini adalah pertanyaan lama tetapi saya mencoba melakukan hal serupa. Saya perlu menjaga agar pekerja tetap berjalan. Mereka terstruktur dalam sebuah janji. Saya perlu memindai dan melihat apakah mereka diselesaikan, ditolak atau masih tertunda. Jika diselesaikan, saya perlu nilainya, jika ditolak melakukan sesuatu untuk memperbaiki masalah atau menunggu keputusan. Jika diselesaikan atau ditolak, saya harus memulai tugas lain untuk terus berjalan. Saya tidak dapat menemukan cara untuk melakukannya dengan Promise.all atau Promise.race karena saya terus bekerja janji dalam array dan tidak dapat menemukan cara untuk menghapusnya. Jadi saya membuat pekerja yang melakukan trik

Saya membutuhkan fungsi generator janji yang mengembalikan janji yang menyelesaikan atau menolak seperlunya. Ini disebut oleh fungsi yang mengatur kerangka kerja untuk mengetahui apa yang dilakukan janji.

Dalam kode di bawah ini generator hanya mengembalikan janji berdasarkan setTimeout.

Ini dia

//argObj should be of form
// {succeed: <true or false, nTimer: <desired time out>}
function promiseGenerator(argsObj) {
  let succeed = argsObj.succeed;          
  let nTimer = argsObj.nTimer;
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (succeed) {
        resolve('ok');
      }
      else {
        reject(`fail`);
      }
    }, nTimer);
  })

}

function doWork(generatorargs) {
  let sp = { state: `pending`, value: ``, promise: "" };
  let p1 = promiseGenerator(generatorargs)
    .then((value) => {
      sp.state = "resolved";
      sp.value = value;
    })
    .catch((err) => {
      sp.state = "rejected";
      sp.value = err;
    })
  sp.promise = p1;
  return sp;
}

doWork mengembalikan objek yang berisi janji dan statusnya serta nilai yang dikembalikan.

Kode berikut menjalankan loop yang menguji negara dan membuat pekerja baru agar tetap di 3 pekerja berjalan.

let promiseArray = [];

promiseArray.push(doWork({ succeed: true, nTimer: 1000 }));
promiseArray.push(doWork({ succeed: true, nTimer: 500 }));
promiseArray.push(doWork({ succeed: false, nTimer: 3000 }));

function loopTimerPromise(delay) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('ok');
    }, delay)
  })
}

async function looper() {
  let nPromises = 3;      //just for breaking loop
  let nloop = 0;          //just for breaking loop
  let i;
  //let continueLoop = true;
  while (true) {
    await loopTimerPromise(900);  //execute loop every 900ms
    nloop++;
    //console.log(`promiseArray.length = ${promiseArray.length}`);
    for (i = promiseArray.length; i--; i > -1) {
      console.log(`index ${i} state: ${promiseArray[i].state}`);
      switch (promiseArray[i].state) {
        case "pending":
          break;
        case "resolved":
          nPromises++;
          promiseArray.splice(i, 1);
          promiseArray.push(doWork({ succeed: true, nTimer: 1000 }));
          break;
        case "rejected":
          //take recovery action
          nPromises++;
          promiseArray.splice(i, 1);
          promiseArray.push(doWork({ succeed: false, nTimer: 500 }));
          break;
        default:
          console.log(`error bad state in i=${i} state:${promiseArray[i].state} `)
          break;
      }
    }
    console.log(``);
    if (nloop > 10 || nPromises > 10) {
      //should do a Promise.all on remaining promises to clean them up but not for test
      break;
    }
  }
}

looper();

Diuji dalam node.js

BTW Bukan dalam jawaban ini tetapi pada topik lain yang serupa, saya BENCI ketika seseorang mengatakan "Anda tidak mengerti" atau "itu bukan cara kerjanya" Saya biasanya menganggap penanya tahu apa yang mereka inginkan. Menyarankan cara yang lebih baik itu bagus. Penjelasan yang sabar tentang bagaimana janji bekerja juga akan baik.

Charles Bisbee
sumber
-1

Saya menemukan solusi ini sederhana dan memungkinkan saya untuk terus menggunakan janji-janji asli tetapi menambahkan pemeriksaan sinkron yang berguna. Saya juga tidak perlu menarik seluruh perpustakaan janji.

CAVEAT: Ini hanya berfungsi jika ada semacam jeda di utas eksekusi saat ini untuk memungkinkan janji untuk dieksekusi SEBELUM memeriksa konstruksi sinkron. Itu membuat kegunaan ini lebih terbatas daripada yang saya pikirkan - masih berguna untuk kasus penggunaan saya (Terima kasih Benjamin Gruenbaum untuk menunjukkan ini)

/**
 * This function allow you to modify a JS Promise by adding some status properties.
 * Based on: http://stackoverflow.com/questions/21485545/is-there-a-way-to-tell-if-an-es6-promise-is-fulfilled-rejected-resolved
 * But modified according to the specs of promises : https://promisesaplus.com/
 */
function MakeQuerablePromise(promise) {
    // Don't modify any promise that has been already modified.
    if (promise.isFulfilled) return promise;

    // Set initial state
    var isPending = true;
    var isRejected = false;
    var isFulfilled = false;

    // Observe the promise, saving the fulfillment in a closure scope.
    var result = promise.then(
        function(v) {
            isFulfilled = true;
            isPending = false;
            return v; 
        }, 
        function(e) {
            isRejected = true;
            isPending = false;
            throw e; 
        }
    );

    result.isFulfilled = function() { return isFulfilled; };
    result.isPending = function() { return isPending; };
    result.isRejected = function() { return isRejected; };
    return result;
}

wrappedPromise = MakeQueryablePromise(Promise.resolve(3)); 
setTimeout(function() {console.log(wrappedPromise.isFulfilled())}, 1);

Dari https://ourcodeworld.com/articles/read/317/how-to-check-if-a-javascript-promise-has-been-fulfilled-rejected-or-resolved yang mendasarkan jawaban mereka pada apakah ada cara untuk memberi tahu apakah janji ES6 dipenuhi / ditolak / diselesaikan?

Akrikos
sumber
Seperti yang ditambahkan dalam komentar Anda pada jawaban saya - ini sepenuhnya salah: itu tidak memungkinkan Anda untuk secara sinkron memeriksa keadaan janji - Misalnya MakeQueryablePromise(Promise.resolve(3)).isResolvedsalah tetapi janji itu jelas-jelas terselesaikan. Belum lagi jawaban itu juga menggunakan istilah "diselesaikan" dan "terpenuhi" salah. Untuk melakukan itu, Anda dapat menambahkan .thenhandler sendiri - yang benar-benar melenceng dari titik pemeriksaan sinkron.
Benjamin Gruenbaum
Saya mengerti apa yang Anda katakan dan Anda membuat poin yang bagus. Sifat threaded tunggal JS semakin menghalangi bukan? Anda harus menghentikan eksekusi saat ini agar janji tersebut ditandai sebagai terselesaikan. let wrappedPromise = MakeQueryablePromise(Promise.resolve(3)); setTimeout(function() {console.log(wrappedPromise.isFulfilled())}, 1);Yang selama Anda melakukan itu, ini bekerja dengan baik. Tetapi Anda harus memahami fakta itu agar ini berguna. Saya akan memperbarui deskripsi dengan peringatan itu. Saya juga setuju bahwa penamaan fungsi bisa lebih baik / lebih idiomatis.
Akrikos
Tetapi pada titik itu Anda hanya bisa thenjanji asli dan mencapai hal yang sama karena itu tetap tidak sinkron. Ada cara process.binding('util').getPromiseDetailsyang sepertinya berhasil tetapi menggunakan API pribadi
Benjamin Gruenbaum
Sangat menjengkelkan jika harus sepanjang waktu dan membuat kode jauh lebih sulit untuk dipahami. Terutama ketika yang saya pedulikan adalah apakah janji telah ditolak atau tidak - jadi pilihan saya adalah menyimpan keadaan itu di tempat lain atau melakukan sesuatu seperti ini. Saya akui saya tidak membaca solusi lain di sini secara menyeluruh sebelum memposting sendiri - permintaan maaf untuk itu. Masalah ini lebih sulit daripada yang saya pikirkan.
Akrikos