ฉันจะกำหนดสถานะของสัญญา JavaScript ได้อย่างไร


149

ฉันมีสัญญา JavaScript บริสุทธิ์ (การติดตั้งในตัวหรือการเติมโพลี):

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

จากข้อกำหนดคุณสมบัติสัญญาสามารถเป็นหนึ่งใน:

  • 'ตัดสิน' และ 'แก้ไขแล้ว'
  • 'ตัดสิน' และ 'ปฏิเสธ'
  • 'รอดำเนินการ'

ฉันมีกรณีการใช้งานที่ฉันต้องการสอบปากคำสัญญาพร้อมกันและกำหนด:

  • สัญญาถูกตัดสินหรือไม่

  • ถ้าเป็นเช่นนั้นสัญญาจะได้รับการแก้ไขหรือไม่

ฉันรู้ว่าฉันสามารถใช้#then()เพื่อกำหนดเวลาการทำงานที่จะดำเนินการแบบอะซิงโครนัสหลังจากสถานะการเปลี่ยนแปลงสัญญา ฉันไม่ได้ถามว่าจะทำอย่างไร

คำถามนี้เป็นคำถามเฉพาะที่เกี่ยวกับการสอบสวนซิงโครของรัฐของสัญญา ฉันจะบรรลุสิ่งนี้ได้อย่างไร


6
ตั้งค่าคุณสมบัติตามสัญญาซึ่งสามารถมองเห็นได้จากภายนอกและใช้ () เพื่อเปลี่ยนคุณสมบัติ
dandavis

@jokeyrhyme FWIW แหล่ง v8 code.google.com/p/v8/source/browse/branches/bleeding_edge/src/...ดูvar promiseStatus = NEW_PRIVATE("Promise#status");, PromiseSetฟังก์ชั่นที่SET_PRIVATE(promise, promiseStatus, status);
guest271314

ที่นี่เราไป: esdiscuss.org/topic/…
jokeyrhyme

ดูเหมือนว่าแปลกถ้าคุณทำ const a = Promise.resolve ('baz'); console.log (ก); และดูในคอนโซล Chrome คุณเห็นสัญญา {[[PromiseStatus]]: "แก้ไข", [[PromiseValue]]: "baz"} โปรโต: สัญญา [[PromiseStatus]]: "แก้ไข" [[PromiseValue]]: "baz "และผู้คนอ้างว่าทำไม่ได้ Chrome ทำงานอย่างไร (กำลังทำสิ่งนี้ใน Plunker ที่มี Angular plnkr.co/edit/IPIWgLJKQStI5ubXmcsF
JGFMK

การใช้โหนด v11.12.0 console.log จะแสดงสถานะสัญญา EG console.log(Promise.new((resolve, reject) => {})=>Promise { <pending> }
Puhlze

คำตอบ:


77

ไม่มี API การตรวจสอบแบบซิงโครนัสสำหรับสัญญาจาวาสคริปต์ดั้งเดิม มันเป็นไปไม่ได้ที่จะทำเช่นนี้กับสัญญาของชาวพื้นเมือง สเปคไม่ได้ระบุวิธีการดังกล่าว

ไลบรารี Userland สามารถทำสิ่งนี้ได้และหากคุณกำหนดเป้าหมายเป็นเครื่องมือเฉพาะ (เช่น v8) และเข้าถึงรหัสแพลตฟอร์ม (นั่นคือคุณสามารถเขียนโค้ดในแกนกลาง ) จากนั้นคุณสามารถใช้เครื่องมือเฉพาะ (เช่นสัญลักษณ์ส่วนตัว) เพื่อให้บรรลุเป้าหมายนี้ . ที่พิเศษสุดแม้ว่าและไม่ได้อยู่ใน userland


4
หมายเหตุ: ฉันเชื่อโดยสุจริตว่ากรณีการใช้งานสำหรับการตรวจสอบแบบซิงโครนัสนั้นมีน้อยและหายากมากหากคุณแบ่งปันคำถามการใช้งานที่เป็นรูปธรรมของคุณด้วยคำถามใหม่ถามว่าจะทำอย่างไรโดยไม่ตรวจสอบแบบซิงโครนัส เอาชนะฉันไป :)
เบนจามิน Gruenbaum

4
แม้ว่ากรณีการใช้งานจะหายากอะไรจะเกิดความเสียหายอะไรรวมถึงสิ่งนี้ ฉันต้องการตรวจสอบสถานะเช่นนี้เพื่อดูว่างานก่อนหน้านี้เสร็จสิ้นหรือไม่และสามารถของานอื่นได้หรือไม่ และฉันไม่สามารถตั้งค่าตัวแปรภายนอกได้เนื่องจากวัตถุนั้นมีศักยภาพที่จะเปลี่ยนเจ้าของโดยไม่ต้องแจ้งให้ทราบล่วงหน้า สิ่งที่น่ารำคาญกว่าคือฉันสามารถ SEE Node.js สามารถเข้าถึงข้อมูลนี้ได้เพราะมันจะแสดงให้ฉันเห็นเมื่อฉันตรวจสอบ แต่ไม่มีวิธีใดที่จะได้รับนอกเหนือจากการวิเคราะห์สตริง?
Tustin2121

9
ดังนั้นเราจะต้องละทิ้งคำสัญญาดั้งเดิมเนื่องจากพวกเขาทำไม่ได้และใช้บลูเบิร์ด ข่าวดี! ฉันจะเสนอคำสัญญาดั้งเดิมเพื่อเลิกใช้และโยนออกจากโหนดเอ็นจินได้อย่างไร
user619271

1
มีหลายสิ่งที่เราควรระบุ.anyและทำผิดเพราะมาร์คยืนยัน สำหรับหนึ่งPromise.race([])คือสัญญาที่ค้างอยู่ตลอดกาล (และไม่ใช่ข้อผิดพลาด) คุณมักต้องการสัญญาที่ประสบความสำเร็จครั้งแรกและไม่ใช่แค่สัญญาแรก อย่างไรก็ตามมันไม่เกี่ยวข้องกับคำถามที่ถาม - OP ถามเกี่ยวกับการตรวจสอบแบบซิงโครนัสและไม่เกี่ยวกับ.raceและข้อบกพร่องมากมาย
Benjamin Gruenbaum

5
@Akrikos คำตอบนั้นไม่อนุญาตให้คุณตรวจสอบสถานะของสัญญา - ตัวอย่างเช่นMakeQueryablePromise(Promise.resolve(3)).isResolvedเป็นเท็จ แต่สัญญานั้นได้รับการแก้ไขอย่างชัดเจน ไม่พูดถึงคำตอบนั้นยังใช้คำว่า "แก้ไข" และ "เติมเต็ม" ไม่ถูกต้อง ในการทำเช่นนั้นคำตอบนั้นคุณสามารถเพิ่ม.thenตัวจัดการได้เองซึ่งพลาดจุดตรวจสอบแบบซิงโครนัสอย่างสมบูรณ์
Benjamin Gruenbaum

31

ป้อนคำอธิบายรูปภาพที่นี่

สัญญาสถานะ - asyncไม่หลอกลวง เป็น async แต่ไม่ได้ใช้thenเพื่อรอสัญญาที่จะแก้ไข

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

4
OP ถามว่าจะทำอย่างไรพร้อมกัน
Klesun

28

ไม่ไม่ใช่ API การซิงค์ แต่นี่เป็น async เวอร์ชันของฉันpromiseState(ด้วยความช่วยเหลือจาก @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


มีเหตุผลเฉพาะที่อยู่เบื้องหลังการก่อสร้างนี้หรือไม่? ดูเหมือนว่าซับซ้อนสำหรับฉันโดยไม่จำเป็น เท่าที่ฉันสามารถบอกได้ว่างานนี้เหมือนกัน: Promise.race([ Promise.resolve(p).then(() => "fulfilled", () => "rejected"), Promise.resolve().then(() => "pending") ]); แม้ว่ามันจะปลอดภัยกว่าสำหรับฉันconst t = {}; return Promise.race([p,t]).then(v => v === t ? "pending" : "fulfilled", () => "rejected") และหลีกเลี่ยงการสร้างคำสัญญาเพิ่มเติมที่คงอยู่ตราบใดที่ p เดิมยังค้างอยู่
Matthijs

ขอบคุณ @ Matthijs! ฉันทำให้คำตอบของฉันง่ายขึ้น
jib

16

คุณสามารถแข่งกับ Promise.resolve
มันไม่ซิงโครนัส แต่เกิดขึ้นตอนนี้

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

สคริปต์เล็กน้อยสำหรับการทดสอบและทำความเข้าใจความหมายของมันแบบอะซิงโครนัส

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');

ผลลัพธ์ที่มีความล่าช้า (0) (แสดงความคิดเห็นในขณะที่ล่าช้า)

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

และผลของการทดสอบนี้กับ firefox (chrome รักษาลำดับ)

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

contractState make .race และ. then: Level 2


3
แทนที่จะ'a value that p should not return'ใช้Symbol
programmer5000

1
@ programmer5000 ประโยชน์คืออะไร?
Moritz Schmitz v. Hülst

2
@ MoritzSchmitzv.Hülst a Symbolจะเป็นค่าที่ไม่เหมือนใครดังนั้นคุณไม่จำเป็นต้องเดาว่า "value [... ] p ไม่ควรกลับมา" อย่างไรก็ตามการอ้างอิงไปยังวัตถุเฉพาะจะทำงานได้เช่นกัน
Scott Rudiger

7

คุณสามารถใช้แฮ็ค (น่าเกลียด) ใน Node.js ได้จนกว่าจะมีวิธีการเนทีฟ:

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
}

3
ฉันได้ต้มมันลงไปในPromise.prototype.isPending = function(){ return util.inspect(this).indexOf("<pending>")>-1; }
พอลิฟิลล์

5
นั่นเป็นที่น่ากลัว
John Weisz

@JohnWeisz สิ่งที่น่ากลัวคือการขาดความเข้ากันได้ย้อนหลัง ฉันพยายามที่จะรวมสัญญา API กับสัญญานฐานซึ่งถือว่าทุกอย่างตรงกัน มันอาจจะทำสิ่งที่น่ากลัวหรือเขียนโค้ดขนาดใหญ่อีกครั้ง ไม่ว่าฉันจะทำอะไร
rath

4
เพียงใช้process.binding('util').getPromiseDetails
อมรา

@ Tustin2121 Promise.resolve('<pending>')สำหรับรุ่นบางอย่างมันจะล้มเหลวกับสิ่งที่ต้องการ
user202729

7

ในโหนดพูด ไม่มีเอกสารภายใน 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> ]

ฉันเพิ่มสิ่งนี้เพราะมันไม่ได้อยู่ในคำตอบที่มีอยู่และสำหรับโหนดมันเป็นคำตอบที่ดีที่สุด มันง่ายที่จะค้นหาเอกสารสำหรับมันในgithub.com/nodejs/node
amara

6

อัปเดต: 2019

Bluebird.js เสนอสิ่งนี้: http://bluebirdjs.com/docs/api/isfulfilled.html

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

หากคุณต้องการสร้างเสื้อคลุมของคุณเองนี่เป็นบล็อกที่ดีเกี่ยวกับมัน

เนื่องจาก JavaScript เป็นเธรดเดี่ยวจึงเป็นการยากที่จะหากรณีการใช้งานทั่วไปพอที่จะพิสูจน์การวางสิ่งนี้ในข้อมูลจำเพาะ สถานที่ที่ดีที่สุดที่จะรู้ว่าสัญญาได้รับการแก้ไขแล้วหรือไม่ () การทดสอบว่าสัญญาแบบเติมเต็มจะสร้างลูปโพลซึ่งเป็นไปในทิศทางที่ผิด

async / awaitเป็นโครงสร้างที่ดีหากคุณต้องการให้เหตุผลรหัส async แบบซิงโครนัส

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

อีกสายที่มีประโยชน์คือ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"]

เมื่อฉันไปถึงคำตอบนี้ครั้งแรกนั่นเป็นกรณีการใช้งานที่ฉันกำลังมองหา


5

คุณสามารถห่อคำสัญญาของคุณด้วยวิธีนี้

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

5
เรื่องนี้จะต้อง OP ได้รับการเข้าถึงสัญญาในการเปิดก่อนหน้านี้ของวงเหตุการณ์ เนื่องจาก.thenดำเนินการแบบอะซิงโครนัส OP ผู้ที่ต้องการตรวจสอบสัญญาในเทิร์นเดียวกันเสมอจะไม่ได้ผลลัพธ์ที่ถูกต้องที่นี่ หมายเหตุ OP ถามเฉพาะเกี่ยวกับการซิงโครการตรวจสอบและบอกว่าพวกเขารู้อยู่แล้วว่าเกี่ยวกับการตรวจสอบไม่ตรงกัน
Benjamin Gruenbaum

@BenjaminGruenbaum: ค่าเริ่มต้นจะไม่เกิดขึ้นหากรหัสใน "เทิร์น" อันเดียวกันเรียกว่าหรือไม่
dandavis

แน่นอนว่าคุณต้องห่อคำสัญญาทั้งหมดไว้ ณ เวลาที่สร้าง เช่นภายในฟังก์ชันที่สร้างและส่งคืน
SpiderPig

3
ถึงตอนนี้พวกเขาก็ไม่ได้สัญญากับชาวพื้นเมืองอีกต่อไปคุณอาจขยายวิธีที่พวกเขาตั้งใจจะขยายด้วยคลาสย่อยซึ่งจะช่วยให้คุณทำสิ่งนี้ได้อย่างสง่างามแทนที่จะเป็นคุณสมบัติการปะของลิงบนวัตถุ
Benjamin Gruenbaum

ไม่ว่าคุณจะขยายสัญญาในแบบที่ฉันแสดงหรือโดยการจัดกลุ่มย่อยในแต่ละกรณีคุณยังคงต้องเพิ่มเวอร์ชันของคุณเองแล้วจับได้
SpiderPig

5

มันค่อนข้างน่ารำคาญจริงๆที่ฟังก์ชั่นพื้นฐานหายไป หากคุณกำลังใช้ node.js ฉันรู้วิธีแก้ปัญหาสองข้อไม่ดีเท่าไหร่ ตัวอย่างทั้งสองด้านล่างใช้ API เดียวกัน:

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

ดูเหมือนจะไม่มีวิธีใดที่จะแยกแยะสถานะสัญญาทั้งสองครั้งล่าสุดโดยใช้กลอุบายใด ๆ

1. ใช้ V8 debug API

นี่เป็นเคล็ดลับเดียวกับที่util.inspectใช้

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. เรียกใช้ microtasks แบบซิงโครนัส

สิ่งนี้หลีกเลี่ยงการดีบัก API แต่มีซีแมนทิคส์ที่น่ากลัวบางอย่างโดยทำให้ microtasks และprocess.nextTickcallback ที่ค้างอยู่ทั้งหมดทำงานแบบซิงโครนัส นอกจากนี้ยังมีผลข้างเคียงของการป้องกันข้อผิดพลาด "การปฏิเสธสัญญาที่ไม่สามารถจัดการได้" จากการถูกเรียกใช้สำหรับสัญญาที่ตรวจสอบ

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

มันไม่ปลอดภัยมากที่จะทำprocess._tickCallback(หรือแม้แต่ธรรมดา% RunMicrotick) - มันจะสุ่มแบ่งสิ่งต่าง ๆ ในรหัสของคุณ ฉันพยายามอย่างยิ่งที่จะให้มันทำงาน (สำหรับตัวจับเวลาปลอมในฟังก์ชั่นแบบอะซิงก์ส่วนใหญ่) และมันไม่เคยมีเสถียรภาพเพียงพอจากฝั่งโหนด ฉันยอมแพ้ในการทำงานกับมัน API มิร์เรอร์ดีบัก V8 เหมาะสมอย่างสมบูรณ์ที่นี่
Benjamin Gruenbaum

และ .. DeprecationWarning: DebugContext has been deprecated and will be removed in a future version.:( ดูเหมือนว่า V8 จะลบออกไปแล้ว
Benjamin Gruenbaum

เรา (โหนด) ทั้งหมดสามารถขอ V8 สำหรับ API หรือเปิดเผย API เพื่อดูสถานะของสัญญาโดยตรง - หากคุณเปิดปัญหาที่github.com/nodejs/promise-use-caseฉันจะนำมันมาด้วย V8 อย่างมีความสุข
Benjamin Gruenbaum

1
ความคิดเห็นเพิ่มเติมในหัวข้อนี้แสดงให้เห็นว่า API ดูเหมือนมีอยู่แล้ว: process.binding('util').getPromiseDetails( promise )ส่งคืน[ 0, ]เพื่อรอดำเนินการ[ 1, value ]เพื่อเติมเต็มและ[ 2, value ]ปฏิเสธ
Matthijs

3

Caveat: วิธีนี้ใช้ internals Node.js ที่ไม่มีเอกสารประกอบและสามารถเปลี่ยนแปลงได้โดยไม่มีการเตือน

ในโหนดคุณสามารถกำหนดสถานะของสัญญาได้พร้อมกัน process.binding('util').getPromiseDetails(/* promise */);กัน

สิ่งนี้จะส่งคืน:

[0, ] สำหรับรอ

[1, /* value */] เพื่อเติมเต็มหรือ

[2, /* value */] สำหรับการปฏิเสธ

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']

ล้อมสิ่งนี้ไว้ในฟังก์ชั่นตัวช่วย:

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

getStatus(pending); // pending
getStatus(fulfilled); // fulfilled
getStatus(rejected); // rejected

ดูเหมือนจะไม่ทำงานจากภายในjest(ซึ่งเป็นสถานที่เดียวที่ฉันสนใจจริงๆ) มีฟังก์ชันอยู่ แต่ดูเหมือนจะกลับมาundefinedเสมอ ฉันจะรู้ได้อย่างไรว่ามีอะไรผิดปกติ?
Adam Barnes

อืมผมจำได้ว่ามันทำงานภายในmocha; ไม่เคยพยายามมันด้วยjestแม้ว่า อาจจะเริ่มต้นคำถามใหม่ที่เชื่อมโยงที่นี่และรวมถึงรุ่น Node.js ของคุณเช่นเดียวกับjestรุ่น?
Scott Rudiger

ไม่ใช่สิ่งที่ฉันสนใจอีกมากขออภัย ฉันมักจะมองหาวิธีทดสอบความมีเหตุผลของตัวเองแก้ไข / ปฏิเสธได้Promiseว่าฉันใช้เพื่อทดสอบสิ่งที่ควรจะเกิดขึ้นในขณะที่อยู่ในPromiseระหว่างรอ แต่ฉันคิดว่าตราบใดที่ฉันเขียนทำงานได้แล้วไม่จำเป็นต้องทดสอบว่า นอกเหนือจากสิ่งที่ต้องใช้
Adam Barnes

2

สิ่งที่คุณสามารถทำได้คือการใช้ตัวแปรเพื่อเก็บสถานะตั้งค่าสถานะเป็นตัวแปรนั้นด้วยตนเองและตรวจสอบตัวแปรนั้น

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

แน่นอนซึ่งหมายความว่าคุณต้องมีสิทธิ์เข้าถึงรหัสต้นฉบับของสัญญา หากคุณไม่ทำเช่นนั้นคุณสามารถทำได้:

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

โซลูชันของฉันมีการเข้ารหัสเพิ่มเติม แต่ฉันคิดว่าคุณคงไม่ต้องทำเช่นนี้สำหรับทุกสัญญาที่คุณใช้



2

คุณสามารถเพิ่มวิธีการที่ Promise.prototype ดูเหมือนว่านี้:

แก้ไข:วิธีแก้ปัญหาแรกทำงานไม่ถูกต้องเหมือนกับคำตอบส่วนใหญ่ที่นี่ มันจะส่งกลับ "รอ" จนกระทั่งฟังก์ชันอะซิงโครนัส ". แล้ว" ถูกเรียกใช้ซึ่งจะไม่เกิดขึ้นทันที (เช่นเดียวกับการแก้ปัญหาโดยใช้ Promise.race) ทางออกที่สองของฉันแก้ปัญหานี้

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

คุณสามารถใช้มันในสัญญาใด ๆ สำหรับตัวอย่าง:

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

โซลูชันที่สอง (และถูกต้อง):

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

และใช้มัน:

ประกาศ : ในโซลูชันนี้คุณไม่จำเป็นต้องใช้โอเปอเรเตอร์ "ใหม่"

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

1

นี่คือเวอร์ชั่น Es6 ที่มีเนื้อออกมามากขึ้นของ QueryablePromise ทำให้สามารถเชื่อมโยงและแก้ไขหลังจากการแก้ปัญหาครั้งแรกและแก้ไขหรือปฏิเสธทันทีเพื่อให้ API สอดคล้องกับสัญญาดั้งเดิม

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>


1

awaitการใช้คำตอบของ @ jibพร้อมการสร้างต้นแบบ

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

โปรดทราบว่าฟังก์ชั่น async นี้รัน "เกือบ" ทันทีเช่นฟังก์ชั่นที่ซิงค์ (หรืออาจจะเป็นทันที)


1

2019:

วิธีง่ายๆในการทำอย่างที่ฉันรู้ก็คือthenableเสื้อคลุมที่บางเบามากที่สุดตามสัญญาหรืองานอะซิงก์ใด ๆ

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

1

คุณสามารถextendคลาส Promise เพื่อสร้างคลาส Promise ที่สืบค้นได้ใหม่

คุณสามารถสร้างคลาสย่อยของคุณเองQueryablePromiseโดยรับมรดกจากPromiseคลาสที่มีอยู่ตามปกติอินสแตนซ์ที่จะมีstatusคุณสมบัติพร้อมใช้งานซึ่งคุณสามารถใช้เพื่อสอบถามสถานะของวัตถุสัญญาพร้อมกัน การดำเนินการของมันสามารถดูด้านล่างหรืออ้างอิงนี้สำหรับคำอธิบายที่ดีกว่า

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)


น่าเสียดายที่ไม่มี API ที่มีอยู่จะส่งคืนคลาสใหม่นี้ คุณจินตนาการว่าผู้คนใช้สิ่งนี้อย่างไร
jib

@ jib ขอบคุณสำหรับการตอบกลับของคุณ คุณหมายความว่าอย่างไรว่า API จะไม่ส่งคืนคลาสนี้ :(
UtkarshPramodGupta

ไม่มีAPI ที่มีอยู่จะส่งคืนเนื่องจากจะต้องมีการเขียนเพื่อส่งคืนใช่ไหม เช่นถ้าฉันเรียกfetchมันว่าจะคืนสัญญาพื้นเมือง ชั้นเรียนของคุณจะช่วยได้อย่างไร
jib

ดีเราไม่สามารถเพียงแค่ห่อที่สามารถดึงข้อมูลการโทรใน QuerablePromise ใหม่ของเราที่ชอบ: const queryableFetch = new QueryablePromise((resolve, reject) => {fetch(/.../).then((data) => resolve(data)) })? หรือมีปัญหากับเรื่องนี้หรือไม่? : /
UtkarshPramodGupta

สิ่งนี้ควรใช้งานได้อย่าลืม, err => reject(err)ว่าข้อโต้แย้งที่สองthenหรือไม่สามารถเผยแพร่ข้อผิดพลาดได้อย่างถูกต้อง (ด้วยเหตุผลหลายประการที่พิจารณาว่าเป็นตัวสร้างรูปแบบการต่อต้านสัญญา ) มันไม่ได้เป็นแบบซิงโครนัสอย่างแท้จริง (เช่นจะไม่ตรวจพบสัญญาที่แก้ไขแล้ว) แต่อาจมีประโยชน์ในกรณีที่คุณไม่ได้ควบคุมผู้โทรและต้องการคำตอบทันที
jib

1

มีอีกวิธีหนึ่งที่หรูหราและแฮ็คในการตรวจสอบว่าสัญญายังคงค้างอยู่เพียงแค่แปลงวัตถุทั้งหมดเป็นสตริงและตรวจสอบด้วยความช่วยเหลือในการตรวจสอบเช่นนี้:util.inspect(myPromise).includes("pending")ของการตรวจสอบถ้าสัญญาที่ยังคงค้างอยู่เพียงโดยการแปลงวัตถุทั้งสตริงและตรวจสอบด้วยความช่วยเหลือของการตรวจสอบเช่นนี้:

ทดสอบบน Node.js 8,9,10,11,12,13

นี่คือตัวอย่างเต็ม

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

ผลลัพธ์:

true
true
false
false
false

0

หากคุณใช้การทดลอง ES7 คุณสามารถใช้ async เพื่อตัดสัญญาที่คุณต้องการฟังได้อย่างง่ายดาย

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

0

ฉันได้เขียนแพ็กเกจ npm เล็ก ๆ น้อย ๆ นั่นคือสัญญาคุ้มค่าซึ่งให้ค่า wrapper ตามสัญญาresolved:

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

นอกจากนี้ยังช่วยให้การเข้าถึงมูลค่าสัญญา (หรือข้อผิดพลาด) แบบซิงโครนัส สิ่งนี้จะไม่เปลี่ยนวัตถุสัญญาโดยทำตามคำสั่งแทนที่จะขยายรูปแบบ


0

นี่เป็นคำถามที่เก่ากว่า แต่ฉันพยายามทำสิ่งที่คล้ายกัน ฉันต้องการให้คนงาน n ดำเนินต่อไป พวกเขามีโครงสร้างในสัญญา ฉันต้องสแกนและดูว่าพวกเขาได้รับการแก้ไขปฏิเสธหรือยังคงค้างอยู่ หากได้รับการแก้ไขฉันต้องมีค่าถ้าถูกปฏิเสธทำบางสิ่งเพื่อแก้ไขปัญหาหรือรอดำเนินการ หากได้รับการแก้ไขหรือปฏิเสธฉันต้องเริ่มงานใหม่เพื่อให้ทำงานต่อไป ฉันไม่สามารถหาวิธีที่จะทำได้ด้วย Promise.all หรือ Promise.race เนื่องจากฉันยังคงทำงานตามสัญญาในอาร์เรย์และไม่สามารถลบได้ ดังนั้นฉันจึงสร้างคนงานที่ทำอุบาย

ฉันต้องการฟังก์ชันตัวสร้างสัญญาที่คืนสัญญาซึ่งแก้ไขหรือปฏิเสธตามความจำเป็น มันถูกเรียกโดยฟังก์ชั่นที่กำหนดกรอบการทำงานให้รู้ว่าสัญญากำลังทำอะไรอยู่

ในรหัสด้านล่างเครื่องกำเนิดไฟฟ้าเพียงแค่ส่งคืนสัญญาตาม setTimeout

นี่มันคือ

//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 ส่งคืนวัตถุที่มีสัญญาและสถานะและค่าที่ส่งคืน

รหัสต่อไปนี้จะทำงานเป็นลูปที่ทดสอบสถานะและสร้างผู้ปฏิบัติงานใหม่เพื่อให้ทำงานที่ 3 คนที่กำลังทำงานอยู่

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

ทดสอบใน node.js

BTW ไม่ได้อยู่ในคำตอบนี้มาก แต่ในคนอื่น ๆ ในหัวข้อที่คล้ายกันฉันเกลียดเมื่อมีคนพูดว่า "คุณไม่เข้าใจ" หรือ "นั่นไม่ใช่วิธีการทำงาน" ฉันมักจะคิดว่าผู้ถามรู้สิ่งที่พวกเขาต้องการ การแนะนำวิธีที่ดีกว่านั้นยอดเยี่ยม คำอธิบายของผู้ป่วยเกี่ยวกับการทำงานของสัญญาก็จะดีเช่นกัน


-1

ฉันพบวิธีแก้ปัญหานี้ง่ายและอนุญาตให้ฉันใช้สัญญาแบบเนทีฟต่อไป แต่เพิ่มการตรวจสอบแบบซิงโครนัสที่มีประโยชน์ ฉันไม่จำเป็นต้องดึงห้องสมุดสัญญาทั้งหมด

CAVEAT: ใช้งานได้เฉพาะในกรณีที่มีการแบ่งบางส่วนในเธรดการดำเนินการปัจจุบันเพื่อให้สัญญาว่าจะดำเนินการก่อนที่จะตรวจสอบโครงสร้างการซิงโครนัส สิ่งนี้ทำให้มีประโยชน์ จำกัด กว่าที่ฉันคิดไว้ในตอนแรก - ยังมีประโยชน์สำหรับกรณีการใช้ของฉัน (ขอบคุณBenjamin Gruenbaumสำหรับการชี้เรื่องนี้)

/**
 * 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);

จากhttps://ourcodeworld.com/articles/read/317/how-to-check-if-a-javascript-promise-has-been-fulfilled-rejected-or-resolvedซึ่งตามคำตอบของพวกเขาในจะมีวิธีการ บอกได้หรือไม่ว่าสัญญา ES6 เป็นจริง / ถูกปฏิเสธ / แก้ไขแล้ว?


ดังที่เพิ่มในความคิดเห็นของคุณในคำตอบของฉัน - มันไม่ถูกต้องทั้งหมด: นั่นไม่อนุญาตให้คุณตรวจสอบสถานะของสัญญา - ตัวอย่างเช่นMakeQueryablePromise(Promise.resolve(3)).isResolvedเป็นเท็จ แต่สัญญาค่อนข้างชัดเจน ไม่ต้องพูดถึงคำตอบที่ใช้คำว่า "แก้ไข" และ "เติมเต็ม" ไม่ถูกต้อง ในการทำเช่นนั้นคำตอบนั้นคุณสามารถเพิ่ม.thenตัวจัดการได้เองซึ่งพลาดจุดตรวจสอบแบบซิงโครนัสอย่างสมบูรณ์
Benjamin Gruenbaum

ฉันเห็นสิ่งที่คุณพูดและคุณทำจุดดี ลักษณะที่เป็นเธรดเดี่ยวของ JS กำลังเข้ามาใช่มั้ย คุณต้องหยุดพักในการดำเนินการปัจจุบันเพื่อให้สัญญาถูกทำเครื่องหมายว่าแก้ไขแล้ว let wrappedPromise = MakeQueryablePromise(Promise.resolve(3)); setTimeout(function() {console.log(wrappedPromise.isFulfilled())}, 1);ซึ่งตราบใดที่คุณทำเช่นนี้ทำงานได้ดี แต่คุณต้องเข้าใจข้อเท็จจริงที่ว่าสิ่งนี้จะเป็นประโยชน์ ฉันจะอัปเดตคำอธิบายด้วยข้อแม้นั้น ฉันยังยอมรับด้วยว่าการตั้งชื่อฟังก์ชั่นน่าจะดีกว่า
Akrikos

แต่ ณ จุดนั้นคุณสามารถทำthenตามสัญญาเดิมและทำสิ่งเดียวกันให้สำเร็จเพราะมันไม่ตรงกัน มีวิธีการprocess.binding('util').getPromiseDetailsที่ใช้งานไม่ได้ แต่ใช้ API ส่วนตัว
Benjamin Gruenbaum

เป็นเรื่องที่น่ารังเกียจที่จะต้องทำตลอดเวลาและทำให้รหัสนั้นยากที่จะเข้าใจมากขึ้น โดยเฉพาะอย่างยิ่งเมื่อทั้งหมดที่ฉันสนใจคือถ้าสัญญาถูกปฏิเสธหรือไม่ - ดังนั้นตัวเลือกของฉันคือการจัดเก็บที่รัฐอื่นหรือทำอะไรเช่นนี้ ฉันยอมรับว่าฉันไม่ได้อ่านวิธีแก้ปัญหาอื่น ๆ ที่นี่อย่างละเอียดก่อนโพสต์ของฉัน - ขอโทษสำหรับสิ่งนั้น ปัญหานี้เหนียวกว่าที่ฉันคิดไว้ในตอนแรก
Akrikos
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.