Làm cách nào tôi có thể xác định đồng bộ trạng thái của Promise JavaScript?


149

Tôi có một Promise JavaScript thuần túy (triển khai tích hợp hoặc đa điền):

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

Từ đặc điểm kỹ thuật , một Promise có thể là một trong:

  • 'giải quyết' và 'giải quyết'
  • 'giải quyết' và 'từ chối'
  • 'đang chờ xử lý'

Tôi có một trường hợp sử dụng mà tôi muốn thẩm vấn Promise một cách đồng bộ và xác định:

  • Lời hứa đã được giải quyết chưa?

  • Nếu vậy, Lời hứa có được giải quyết không?

Tôi biết rằng tôi có thể sử dụng #then()để lên lịch làm việc để được thực hiện không đồng bộ sau trạng thái thay đổi Promise. Tôi KHÔNG hỏi làm thế nào để làm điều này.

Câu hỏi này đặc biệt về thẩm vấn đồng bộ trạng thái của Promise . Làm thế nào tôi có thể đạt được điều này?


6
đặt một thuộc tính theo lời hứa có thể nhìn thấy từ bên ngoài và sau đó sử dụng () để thay đổi thuộc tính.
dandavis

@jokeyrhyme fwiw, nguồn v8 code.google.com/p/v8/source/browse/branches/bleeding_edge/src/... thấy var promiseStatus = NEW_PRIVATE("Promise#status");, PromiseSetchức năng tạiSET_PRIVATE(promise, promiseStatus, status);
guest271314

Ở đây chúng tôi đi: esdiscuss.org/topic/ Mạnh
jokeyrhyme

Có vẻ kỳ lạ là nếu bạn làm const a = Promise.resolve ('baz'); console.log (a); và tìm trong bảng điều khiển Chrome, bạn thấy Promise {[[PromiseStatus]]: "đã giải quyết", [[PromiseValue]]: "baz"} proto: Promise [[PromiseStatus]]: "đã giải quyết" [[PromiseValue]]: "baz "Và mọi người tuyên bố nó không thể được thực hiện. Chrome đang làm điều đó như thế nào? (đã thực hiện việc này trong Plunker với Angular plnkr.co/edit/IPIWgLJKQStI5ubXmcsF
JGFMK

Sử dụng nút v11.12.0 console.log sẽ hiển thị trạng thái hứa. EG console.log(Promise.new((resolve, reject) => {})=>Promise { <pending> }
Puhlze

Câu trả lời:


77

Không có API kiểm tra đồng bộ như vậy tồn tại cho các lời hứa JavaScript gốc. Không thể làm điều này với những lời hứa bản địa. Các đặc điểm kỹ thuật không chỉ định một phương pháp như vậy.

Thư viện Userland có thể làm điều này và nếu bạn đang nhắm mục tiêu một công cụ cụ thể (như v8) và có quyền truy cập vào mã nền tảng (nghĩa là bạn có thể viết mã trong lõi ) thì bạn có thể sử dụng các công cụ cụ thể (như ký hiệu riêng) để đạt được điều này . Đó là siêu cụ thể mặc dù và không phải trong người dùng.


4
Lưu ý: Tôi thành thật tin rằng các trường hợp sử dụng để kiểm tra đồng bộ là rất ít và rất hiếm, nếu bạn chia sẻ trường hợp sử dụng cụ thể của mình trong một câu hỏi mới hỏi làm thế nào để đạt được nó mà không cần kiểm tra đồng bộ - tôi sẽ trả lời nếu có ai đó sẽ không đánh bại tôi với nó :)
Benjamin Gruenbaum

4
Ngay cả khi các trường hợp sử dụng là hiếm, tác hại của việc này bao gồm những thứ như thế này sẽ làm gì? Tôi sẽ cần kiểm tra trạng thái như thế này để xem công việc trước đó đã kết thúc chưa và liệu tôi có thể yêu cầu một công việc khác không. Và tôi không thể chỉ đặt một biến ngoài vì đối tượng có khả năng thay đổi chủ sở hữu mà không cần thông báo trước. Điều khó chịu hơn là tôi có thể XEM Node.js có quyền truy cập vào thông tin này bởi vì nó hiển thị cho tôi khi tôi kiểm tra nó, nhưng không có cách nào để có được nó ngoài việc phân tích chuỗi ??
Tustin2121

9
Vì vậy, chúng ta phải loại bỏ những lời hứa bản địa vì chúng không thực tế và luôn sử dụng bluebird. Tin tốt! Làm thế nào để tôi đề xuất lời hứa bản địa trở nên phản đối và bị loại khỏi công cụ nút?
dùng619271

1
.anyThay vào đó, có rất nhiều điều đáng lẽ chúng ta phải suy đoán và mắc lỗi vì Mark khăng khăng. Đối với một, Promise.race([])là một lời hứa chờ đợi mãi mãi (và không phải là một lỗi), bạn thường muốn lời hứa thành công đầu tiên và không chỉ là lời hứa đầu tiên. Dù sao, điều đó không thực sự phù hợp với câu hỏi được hỏi - OP đã hỏi về kiểm tra đồng bộ và không phải về .racevà nhiều thiếu sót của nó.
Benjamin Gruenbaum

5
@Akrikos câu trả lời đó không cho phép bạn kiểm tra đồng bộ trạng thái của một lời hứa - Ví dụ MakeQueryablePromise(Promise.resolve(3)).isResolvedlà sai nhưng lời hứa hoàn toàn được giải quyết. Chưa kể câu trả lời đó cũng sử dụng thuật ngữ "đã giải quyết" và "hoàn thành" không chính xác. Để làm được câu trả lời đó, bạn có thể chỉ cần thêm một .thentrình xử lý - điều này hoàn toàn bỏ lỡ điểm kiểm tra đồng bộ.
Benjamin Gruenbaum

31

nhập mô tả hình ảnh ở đây

lời hứa-trạng thái không đồng bộ thực hiện các mẹo. Đó là async nhưng nó không sử dụng thenđể chờ lời hứa được giải quyết.

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 đã hỏi về cách thực hiện đồng bộ mặc dù
Klesun

28

Không, không có API đồng bộ hóa, nhưng đây là phiên bản async của tôi promiseState(với sự trợ giúp từ @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


Có một lý do cụ thể đằng sau công trình này? Nó có vẻ phức tạp không cần thiết với tôi. Theo như tôi có thể nói điều này hoạt động giống hệt nhau: Promise.race([ Promise.resolve(p).then(() => "fulfilled", () => "rejected"), Promise.resolve().then(() => "pending") ]); Mặc dù điều này có vẻ an toàn hơn với tôi: const t = {}; return Promise.race([p,t]).then(v => v === t ? "pending" : "fulfilled", () => "rejected") và tránh tạo ra những lời hứa bổ sung vẫn tồn tại chừng nào p gốc đang chờ xử lý.
Matthijs

Cảm ơn @Matthijs! Tôi đã đơn giản hóa câu trả lời của mình.
jib

16

Bạn có thể thực hiện một cuộc đua với Promise.resolve
Nó không đồng bộ nhưng xảy ra ngay bây giờ

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

Một tập lệnh nhỏ để kiểm tra và hiểu ý nghĩa của chúng không đồng bộ

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

kết quả với độ trễ (0) (nhận xét trong khi trì hoãn)

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

và kết quả của thử nghiệm này với firefox (chrome giữ trật tự)

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

PromiseState thực hiện .race và .then: Cấp 2


3
Thay vì 'a value that p should not return'sử dụng Biểu tượng
lập trình

1
@ lập trình5000 lợi ích là gì?
Moritz Schmitz v. Hülst

2
@ MoritzSchmitzv.Hülst a Symbolsẽ là một giá trị duy nhất, do đó bạn sẽ không bao giờ phải đoán "giá trị [...] p không nên trả về". Tuy nhiên, một tham chiếu đến một đối tượng cụ thể cũng sẽ hoạt động tốt.
Scott Rudiger

7

Bạn có thể sử dụng hack (xấu xí) trong Node.js cho đến khi phương thức gốc được cung cấp:

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
Tôi đã đun sôi nó xuống một polyfill:Promise.prototype.isPending = function(){ return util.inspect(this).indexOf("<pending>")>-1; }
Tustin2121

5
Điều đó thật kinh khủng .
John Weisz

@Johnweisz Điều khủng khiếp là thiếu khả năng tương thích ngược. Tôi đang cố gắng tích hợp API hứa hẹn với một cơ sở mã, giả định mọi thứ đều đồng bộ. Nó đang làm một cái gì đó khủng khiếp hoặc viết lại những đoạn mã lớn. Dù bằng cách nào tôi cũng phạm tội tàn bạo.
Rath

4
chỉ cần sử dụngprocess.binding('util').getPromiseDetails
amara

@ Tustin2121 Đối với một số phiên bản, nó sẽ thất bại với một cái gì đó như Promise.resolve('<pending>').
dùng202729

7

trong nút, nói nội bộ không có giấy tờ 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> ]

tôi đã thêm điều này bởi vì nó không có trong bất kỳ câu trả lời hiện có nào và đối với nút đó là câu trả lời tốt nhất. thật dễ dàng để tìm kiếm các tài liệu cho nó trong github.com/nodejs/node
amara

6

Cập nhật: 2019

Bluebird.js cung cấp điều này: http://bluebirdjs.com/docs/api/isfulfills.html

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

Nếu bạn muốn tạo trình bao bọc của riêng mình, đây là một blog hay về nó.

Vì JavaScript là một luồng đơn, nên thật khó để tìm thấy một trường hợp sử dụng đủ phổ biến để biện minh cho việc đưa cái này vào thông số kỹ thuật. Nơi tốt nhất để biết nếu một lời hứa được giải quyết là trong .then (). Việc kiểm tra nếu một Promise được điền đầy đủ sẽ tạo ra một vòng bỏ phiếu rất có thể là hướng sai.

async / await là một cấu trúc đẹp nếu bạn muốn lý giải mã async một cách đồng bộ.

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

Một cuộc gọi hữu ích khác là 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"]

Khi tôi lần đầu tiên đạt được câu trả lời này, đó là trường hợp sử dụng mà tôi đang tìm kiếm.


5

Bạn có thể thực hiện lời hứa của mình theo cách này

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
Điều này sẽ yêu cầu OP có quyền truy cập vào lời hứa trong lượt trước của vòng lặp sự kiện . Vì .thenluôn luôn thực thi OP không đồng bộ, những người muốn kiểm tra một lời hứa trong cùng một lượt sẽ không nhận được kết quả chính xác ở đây. Lưu ý OP đã hỏi cụ thể về kiểm tra đồng bộ và đề cập rằng họ đã biết về kiểm tra không đồng bộ.
Benjamin Gruenbaum

@BenjaminGruenbaum: các giá trị mặc định sẽ xuất hiện nếu mã trên cùng một "lượt" được gọi?
dandavis

Tất nhiên, bạn phải thực hiện tất cả các lời hứa của mình tại thời điểm tạo. ví dụ bên trong các hàm tạo và trả về chúng.
SpiderPig

3
Đúng vậy, tại thời điểm chúng không thực sự là lời hứa bản địa nữa, bạn cũng có thể mở rộng chúng theo cách chúng được mở rộng với phân lớp cho phép bạn thực hiện điều này một cách tao nhã thay vì các thuộc tính vá khỉ trên một đối tượng.
Benjamin Gruenbaum

Cho dù bạn mở rộng lời hứa theo cách tôi đã trình bày hoặc bằng cách phân loại phụ, trong mỗi trường hợp, bạn vẫn phải thêm phiên bản của riêng mình sau đó và bắt.
SpiderPig

5

Thật sự khá khó chịu khi chức năng cơ bản này bị thiếu. Nếu bạn đang sử dụng node.js thì tôi biết hai cách giải quyết, cả hai đều không đẹp. Cả hai đoạn dưới đây đều thực hiện cùng một 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' }

Dường như không có cách nào để phân biệt hai trạng thái hứa hẹn cuối cùng bằng cách sử dụng một trong hai mẹo.

1. Sử dụng API gỡ lỗi V8

Đây là thủ thuật tương tự mà util.inspectsử dụng.

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. Chạy đồng bộ microt Nhiệm vụ

Điều này tránh API gỡ lỗi, nhưng có một số ngữ nghĩa đáng sợ bằng cách khiến tất cả các microt Nhiệm vụ và process.nextTickcuộc gọi lại đang chờ được chạy đồng bộ. Nó cũng có tác dụng phụ trong việc ngăn chặn lỗi "từ chối lời hứa chưa được xử lý" không bao giờ được kích hoạt cho lời hứa đã được kiểm tra.

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

Nó rất không an toàn để làm process._tickCallback(hoặc thậm chí là% RunMicrotick đơn giản) - nó sẽ ngẫu nhiên phá vỡ mọi thứ trong mã của bạn. Tôi đã rất cố gắng để nó hoạt động (chủ yếu là các bộ định thời giả trong các chức năng không đồng bộ) và nó không bao giờ đủ ổn định từ phía Node. Tôi loại bỏ từ bỏ làm việc trên nó. API gương gỡ lỗi V8 hoàn toàn thích hợp ở đây.
Benjamin Gruenbaum

Và .. DeprecationWarning: DebugContext has been deprecated and will be removed in a future version.:( Hình như V8 đã loại bỏ nó
Benjamin Gruenbaum

Chúng tôi (Node) hoàn toàn có thể yêu cầu V8 cho API hoặc hiển thị API để xem trực tiếp trạng thái của lời hứa - nếu bạn mở một vấn đề tại github.com/nodejs/promise-use-case tôi sẽ vui lòng đưa nó lên với V8
Benjamin Gruenbaum

1
Một bình luận tiếp theo trong chủ đề này đã tiết lộ rằng API đã tồn tại: process.binding('util').getPromiseDetails( promise )trả về [ 0, ]cho việc chờ xử lý, [ 1, value ]để hoàn thành và [ 2, value ]bị từ chối.
Matthijs

3

Hãy cẩn thận: Phương pháp này sử dụng nội bộ Node.js không có giấy tờ và có thể được thay đổi mà không cần cảnh báo.

Trong Node, bạn có thể xác định đồng bộ trạng thái của lời hứa bằng cách sử dụng process.binding('util').getPromiseDetails(/* promise */);.

Điều này sẽ trở lại:

[0, ] để chờ xử lý,

[1, /* value */] để hoàn thành, hoặc

[2, /* value */] cho bị từ chối.

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

Gói cái này vào một hàm trợ giúp:

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

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

Dường như không hoạt động từ bên trong jest(thực sự là nơi duy nhất tôi quan tâm đến nó). Các chức năng tồn tại, nhưng dường như luôn luôn trở lại undefined. Làm thế nào để tôi tìm ra những gì sai?
Adam Barnes

Hmm, tôi nhớ nó hoạt động trong mocha; không bao giờ thử nó với jestmặc dù. Có thể bắt đầu một câu hỏi mới liên kết ở đây và bao gồm phiên bản Node.js cũng như jestphiên bản của bạn?
Scott Rudiger

Không có gì tôi quan tâm đến rất nhiều nữa, thật không may. Về cơ bản, tôi đang tìm cách kiểm tra sự tỉnh táo có thể giải quyết bằng tay / có thể từ chối Promisemà tôi chỉ sử dụng để kiểm tra những thứ sẽ diễn ra trong khi Promiseđang chờ xử lý, nhưng tôi đã tìm ra chừng nào những gì tôi viết, hoạt động, thì không cần phải kiểm tra ngoài những gì dựa vào nó.
Adam Barnes

2

những gì bạn có thể làm là sử dụng một biến để lưu trữ trạng thái, tự đặt trạng thái cho biến đó và kiểm tra biến đó.

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

Tất nhiên, điều này có nghĩa là bạn phải có quyền truy cập vào mã gốc của lời hứa. Nếu bạn không, thì bạn có thể làm:

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

Giải pháp của tôi là mã hóa nhiều hơn, nhưng tôi nghĩ có lẽ bạn sẽ không phải làm điều này cho mỗi lời hứa bạn sử dụng.



2

Bạn có thể thêm một phương thức vào Promise.prototype. Nó trông như thế này:

Đã chỉnh sửa: Giải pháp đầu tiên không hoạt động đúng, giống như hầu hết các câu trả lời ở đây. Nó trả về "đang chờ xử lý" cho đến khi hàm không đồng bộ ".then" được gọi, điều này không xảy ra ngay lập tức. (Tương tự là về các giải pháp sử dụng Promise.race). Giải pháp thứ hai của tôi giải quyết vấn đề này.

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

Bạn có thể sử dụng nó trên bất kỳ Promise. Ví dụ như:

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

Giải pháp thứ hai (và chính xác):

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

Và sử dụng nó:

Lưu ý : Trong giải pháp này, bạn không phải sử dụng toán tử "mới".

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

1

Đây là phiên bản es6 mạnh mẽ hơn của QueryablePromise, cho phép khả năng xâu chuỗi và bắt sau khi giải quyết lần đầu và giải quyết ngay lập tức hoặc từ chối để giữ api phù hợp với Promise gốc.

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

awaitsử dụng cho câu trả lời của @ jib , với nguyên mẫu thành ngữ.

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

lưu ý rằng chức năng async này thực thi "gần như" ngay lập tức như chức năng được đồng bộ hóa (hoặc thực sự có thể là ngay lập tức).


1

2019:

Cách đơn giản để làm điều đó như tôi biết là thenable, trình bao bọc siêu mỏng xung quanh lời hứa hoặc bất kỳ công việc không đồng bộ nào.

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

Bạn có thể extendlớp Promise để tạo ra một mới queryable Promise lớp.

Bạn có thể tạo lớp con của riêng mình QueryablePromise, bằng cách kế thừa từ Promiselớp có sẵn , các trường hợp sẽ có một thuộc statustính có sẵn trên đó mà bạn có thể sử dụng để truy vấn trạng thái của các đối tượng lời hứa một cách đồng bộ . Một triển khai của nó có thể được nhìn thấy dưới đây hoặc tham khảo điều này để giải thích tốt hơn.

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)


Thật không may, không có API hiện tại sẽ trả về lớp mới này. Làm thế nào bạn tưởng tượng mọi người sử dụng này?
jib

@jib Cảm ơn phản hồi của bạn. Bạn có ý nghĩa gì khi không có API nào trả về lớp này? :(
UtkarshPramodGupta

Không API hiện có sẽ trả lại nó, bởi vì chúng sẽ phải được viết để trả lại, phải không? Ví dụ, nếu tôi gọi fetchnó sẽ trả lại một lời hứa bản địa. Làm thế nào lớp học của bạn sẽ giúp với điều đó?
jib

Chà, chúng ta không thể thực hiện cuộc gọi tìm nạp đó trong QuerablePromise mới của chúng tôi như : const queryableFetch = new QueryablePromise((resolve, reject) => {fetch(/.../).then((data) => resolve(data)) })? Hoặc, có một vấn đề với điều đó? : /
UtkarshPramodGupta

Điều đó sẽ hoạt động, chỉ cần đừng quên , err => reject(err)như là một đối số thứ hai thenhoặc nó sẽ không truyền lỗi chính xác (trong số các lý do nó được coi là mô hình chống xây dựng lời hứa ). Mặc dù nó không thực sự đồng bộ (ví dụ: sẽ không phát hiện ra một lời hứa đã được giải quyết), nhưng có lẽ hữu ích trong trường hợp bạn không kiểm soát người gọi và cần có câu trả lời ngay lập tức.
jib

1

Có một cách kiểm tra thanh lịch & hacky khác nếu một lời hứa vẫn đang chờ xử lý chỉ bằng cách chuyển đổi toàn bộ đối tượng thành chuỗi và kiểm tra nó với sự trợ giúp của kiểm tra như sau : util.inspect(myPromise).includes("pending").

Đã thử nghiệm trên Node.js 8,9,10,11,12,13

Đây là một ví dụ đầy đủ

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

Kết quả:

true
true
false
false
false

0

Nếu bạn đang sử dụng thử nghiệm ES7, bạn có thể sử dụng async để dễ dàng thực hiện lời hứa bạn muốn nghe.

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

Tôi đã viết một gói npm nhỏ, giá trị lời hứa, cung cấp một trình bao bọc lời hứa với một resolvedcờ:

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

Nó cũng cho phép truy cập đồng bộ vào giá trị lời hứa (hoặc lỗi). Điều này không làm thay đổi chính đối tượng Promise, theo mẫu bao bọc hơn là mở rộng mẫu.


0

Đây là câu hỏi cũ hơn nhưng tôi đã cố gắng làm một cái gì đó tương tự. Tôi cần phải giữ n công nhân đi. Chúng được cấu trúc trong một lời hứa. Tôi cần quét và xem nếu chúng được giải quyết, từ chối hoặc vẫn đang chờ xử lý. Nếu được giải quyết, tôi cần giá trị, nếu bị từ chối, hãy làm gì đó để sửa lỗi hoặc chờ xử lý. Nếu giải quyết hoặc từ chối tôi cần bắt đầu một nhiệm vụ khác để tiếp tục n. Tôi không thể tìm ra cách để làm điều đó với Promise.all hoặc Promise.race khi tôi tiếp tục thực hiện các lời hứa trong một mảng và không thể tìm cách xóa chúng. Vì vậy, tôi tạo ra một công nhân thực hiện các mẹo

Tôi cần một hàm tạo lời hứa trả về một lời hứa giải quyết hoặc từ chối khi cần thiết. Nó được gọi bởi một chức năng thiết lập khung để biết lời hứa đang làm gì.

Trong mã bên dưới, trình tạo chỉ cần trả về một lời hứa dựa trên setTimeout.

Nó đây rồi

//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 trả về một đối tượng chứa lời hứa và trạng thái của nó và giá trị được trả về.

Đoạn mã sau chạy một vòng lặp kiểm tra trạng thái và tạo ra các công nhân mới để giữ nó ở 3 công nhân đang chạy.

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

Đã thử nghiệm trong node.js

BTW Không phải trong câu trả lời này quá nhiều nhưng ở những người khác về các chủ đề tương tự, tôi ghét nó khi ai đó nói "bạn không hiểu" hoặc "đó không phải là cách nó hoạt động" Tôi thường cho rằng người hỏi biết họ muốn gì. Đề xuất một cách tốt hơn là tuyệt vời. Một lời giải thích kiên nhẫn về cách hứa hẹn làm việc cũng sẽ tốt.


-1

Tôi thấy giải pháp này đơn giản và cho phép tôi tiếp tục sử dụng các lời hứa riêng nhưng thêm các kiểm tra đồng bộ hữu ích. Tôi cũng không phải kéo vào toàn bộ thư viện lời hứa.

CAVEAT: Điều này chỉ hoạt động nếu có một số loại ngắt trong luồng thực thi hiện tại để cho phép các lời hứa thực thi TRƯỚC KHI kiểm tra các cấu trúc đồng bộ. Điều đó làm cho tính hữu dụng này hạn chế hơn so với tôi nghĩ ban đầu - vẫn hữu ích cho trường hợp sử dụng của tôi (Cảm ơn Benjamin Gruenbaum vì đã chỉ ra điều này)

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

Từ https://ourcodeworld.com/articles/read/317/how-to-check-if-a-javascript-promise-has-been-fulfilled-rejected-or-resolved mà dựa câu trả lời của họ trên là có một cách để cho biết nếu một lời hứa ES6 được thực hiện / từ chối / giải quyết?


Như đã thêm trong nhận xét của bạn về câu trả lời của tôi - điều này hoàn toàn không chính xác: điều đó không cho phép bạn kiểm tra đồng bộ trạng thái của một lời hứa - Ví dụ MakeQueryablePromise(Promise.resolve(3)).isResolvedlà sai nhưng lời hứa hoàn toàn được giải quyết. Chưa kể câu trả lời đó cũng sử dụng thuật ngữ "đã giải quyết" và "hoàn thành" không chính xác. Để làm được câu trả lời đó, bạn có thể chỉ cần thêm một .thentrình xử lý - điều này hoàn toàn bỏ lỡ điểm kiểm tra đồng bộ.
Benjamin Gruenbaum

Tôi thấy những gì bạn đang nói và bạn làm cho một điểm tốt. Bản chất đơn luồng của JS đang cản trở phải không? Bạn phải tạm dừng thực hiện hiện tại để lời hứa được đánh dấu là đã giải quyết. let wrappedPromise = MakeQueryablePromise(Promise.resolve(3)); setTimeout(function() {console.log(wrappedPromise.isFulfilled())}, 1);Miễn là bạn làm điều đó, điều này hoạt động độc đáo. Nhưng bạn phải hiểu rằng thực tế này là hữu ích. Tôi sẽ cập nhật mô tả với cảnh báo đó. Tôi cũng đồng ý rằng việc đặt tên hàm có thể tốt hơn / thành ngữ hơn.
Akrikos

Nhưng tại thời điểm đó, bạn có thể chỉ cần thenlời hứa ban đầu và thực hiện điều tương tự vì dù sao nó không đồng bộ. Có một cách process.binding('util').getPromiseDetailsmà nó dường như không hoạt động nhưng đó là sử dụng API riêng
Benjamin Gruenbaum

Thật đáng ghét khi lúc nào cũng phải làm cho mã trở nên khó hiểu hơn nhiều. Đặc biệt là khi tất cả những gì tôi quan tâm là liệu lời hứa có bị từ chối hay không - vì vậy các lựa chọn của tôi là lưu trữ trạng thái đó ở một nơi khác hoặc làm điều gì đó như thế này. Tôi thừa nhận tôi đã không đọc kỹ các giải pháp khác ở đây trước khi đăng bài của riêng mình - xin lỗi vì điều đó. Vấn đề này còn khó khăn hơn tôi nghĩ lúc đầu.
Akrikos
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.