Làm thế nào để tôi biết nếu một đối tượng là một lời hứa?


335

Cho dù đó là Lời hứa ES6 hay Lời hứa bluebird, Lời hứa Q, v.v.

Làm cách nào để kiểm tra xem liệu một đối tượng đã cho có phải là Lời hứa không?


3
Tốt nhất bạn có thể kiểm tra một .thenphương pháp, nhưng điều đó sẽ không cho bạn biết rằng những gì bạn có là một Lời hứa dứt khoát. Tất cả những gì bạn sẽ biết vào thời điểm đó là bạn có một cái gì đó phơi bày một .thenphương thức, như một Lời hứa.
Scott Offen

@Scott Offeren đặc tả lời hứa rõ ràng không tạo ra sự khác biệt.
Benjamin Gruenbaum

6
Quan điểm của tôi là bất kỳ ai cũng có thể tạo ra một đối tượng phơi bày một .thenphương thức không phải là Lời hứa, không hành xử như một Lời hứa và không có ý định sử dụng như một Lời hứa. Kiểm tra một .thenphương thức chỉ cho bạn biết rằng nếu đối tượng không.thenphương thức, thì bạn không có Promise. Điều ngược lại - rằng sự tồn tại của một .thenphương pháp có nghĩa là bạn làm có một Promise - không hẳn đã đúng.
Scott Offen

3
@Scott Offeren Theo định nghĩa, cách duy nhất để xác định lời hứa là kiểm tra xem nó có.then phương thức hay không. Vâng, điều đó có khả năng cho các kết quả dương tính giả, nhưng đó là giả định rằng tất cả các thư viện hứa hẹn đều dựa vào (vì đó là tất cả những gì họ có thể dựa vào). Sự thay thế duy nhất theo như tôi có thể thấy là lấy lời đề nghị của Benjamin Gruenbaum và chạy nó thông qua bộ thử nghiệm lời hứa. Nhưng điều đó không thực tế đối với mã sản xuất thực tế.
JLRishe

Câu trả lời:


341

Làm thế nào một thư viện hứa

Nếu nó có .thenchức năng - đó là thư viện hứa hẹn tiêu chuẩn duy nhất sử dụng.

Đặc tả Promise / A + có một khái niệm gọi là thenkhả năng, về cơ bản là "một đối tượng với một thenphương thức". Hứa sẽ và sẽ đồng hóa bất cứ điều gì với một phương pháp sau đó. Tất cả các thực hiện lời hứa bạn đã đề cập làm điều này.

Nếu chúng ta nhìn vào đặc điểm kỹ thuật :

2.3.3.3 nếu thenlà một hàm, hãy gọi nó bằng x như thế này, đối số thứ nhất notifyPromise và đối số thứ hai từ chốiPromise

Nó cũng giải thích lý do cho quyết định thiết kế này:

Cách xử lý thenables này cho phép các triển khai hứa hẹn có thể tương tác với nhau, miễn là chúng đưa ra thenphương pháp Promising / A + -compliant . Nó cũng cho phép triển khai Promise / A + để đồng hóa các triển khai không phù hợp với các phương thức sau đó hợp lý.

Bạn nên quyết định như thế nào

Bạn không nên - thay vào đó hãy gọi Promise.resolve(x)( Q(x)trong Q) sẽ luôn luôn chuyển đổi bất kỳ giá trị hoặc thenkhả năng bên ngoài nào thành một lời hứa đáng tin cậy. Nó an toàn và dễ dàng hơn so với việc tự thực hiện các kiểm tra này.

có thật không cần phải chắc chắn?

Bạn luôn có thể chạy nó thông qua bộ thử nghiệm : D


95
Câu trả lời chính xác. Một nhận xét mặc dù: "Bạn không nên - thay vào đó hãy gọi Promise.resolve (x)" không phải lúc nào cũng là một lựa chọn. Nếu bạn thực sự muốn kiểm tra xem một đối tượng 'có phải là một lời hứa' hay không ( thì có thể), Promise.resolvesẽ không cho bạn biết bất cứ điều gì và sẽ chuyển đổi các đối tượng trong quy trình. Vì vậy, thực sự câu trả lời sẽ là (đưa ra một đối tượng có tên subject):var isPromise = typeof subject.then == 'function';
Stijn de Witt

11
-1. Tất cả các Lời hứa đều có một thenphương thức, nhưng tất cả các thenphương thức không thuộc về a Promise. Do đó, tuyên bố của bạn không hợp lệ do đó không trả lời câu hỏi.
Linus Oleander

18
@Oleander nghĩa đen là cách nó được định nghĩa trong đặc tả ECMAScript và đặc tả Promise / A +. Nếu nó có một thenphương pháp sau đó nó sẽ được coi như một lời hứa và cần được đệ quy đồng hóa bởi Promise.prototype.then, Promise.resolve all racehoặc các fulfilltham số của các nhà xây dựng lời hứa. Vì vậy, bạn có thể kiểm tra kỹ thuật khác nhau nhưng ngôn ngữ tự kiểm tra thường xuyên và nó kiểm tra như thế này. Tôi không chắc ý kiến ​​của bạn có ý nghĩa gì hoặc không chính xác về điều đó vì phần bạn đang bình luận bắt đầu với "Thư viện lời hứa quyết định như thế nào" và điều đó đúng
Benjamin Gruenbaum

5
@Sebastian hãy thử chờ đợi điều đó và bạn sẽ ngạc nhiên về những gì xảy ra.
Benjamin Gruenbaum

14
Chủ đề này là bực bội để đọc. JavaScript là một ngôn ngữ gõ vịt. Lời hứa không phải là một điều kỳ lạ trong khía cạnh này. Đây là một câu trả lời tuyệt vời.
ChrisM

168

Kiểm tra nếu một cái gì đó được hứa hẹn làm phức tạp mã không cần thiết, chỉ cần sử dụng Promise.resolve

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

})

1
vậy Promise.resolve có thể xử lý bất cứ điều gì xảy ra theo cách của nó? Chắc chắn không có gì, nhưng tôi đoán bất cứ điều gì hợp lý?
Alexander Mills

3
@AlexMills có, nó thậm chí hoạt động cho những lời hứa không chuẩn như lời hứa của jQuery. Nó có thể thất bại nếu đối tượng có một phương thức then có giao diện hoàn toàn khác với lời hứa sau đó.
Esailija

19
Câu trả lời này, mặc dù có lẽ là lời khuyên tốt, nhưng không thực sự trả lời câu hỏi.
Stijn de Witt

4
Trừ khi câu hỏi thực sự là về việc ai đó thực sự thực hiện thư viện lời hứa, thì câu hỏi không hợp lệ. Chỉ có một thư viện lời hứa sẽ cần thực hiện kiểm tra, sau đó bạn luôn có thể sử dụng phương thức .resolve như tôi đã trình bày.
Esailija

4
@Esalija Câu hỏi đối với tôi có vẻ phù hợp và quan trọng, không chỉ với người thực hiện thư viện lời hứa. Nó cũng có liên quan đến người dùng của thư viện lời hứa muốn biết cách triển khai sẽ / nên / có thể hành xử và các thư viện lời hứa khác nhau sẽ tương tác với nhau như thế nào. Đặc biệt, người dùng này rất thất vọng bởi thực tế rõ ràng là tôi có thể đưa ra lời hứa về X cho bất kỳ X nào trừ khi X là "lời hứa" (bất cứ "lời hứa" nào có nghĩa ở đây-- đó là câu hỏi), và tôi chắc chắn rất thích thú trong việc biết chính xác ranh giới của ngoại lệ đó nằm ở đâu.
Don nở

103

Đây là câu trả lời ban đầu của tôi, từ đó đã được phê chuẩn trong thông số kỹ thuật như là cách để kiểm tra lời hứa:

Promise.resolve(obj) == obj

Điều này hoạt động bởi vì thuật toán yêu cầu rõ ràng Promise.resolvephải trả về đối tượng chính xác được truyền vào khi và chỉ khi đó là một lời hứa theo định nghĩa của thông số kỹ thuật.

Tôi có một câu trả lời khác ở đây, thường dùng để nói điều này, nhưng tôi đã thay đổi nó thành một thứ khác khi nó không hoạt động với Safari vào thời điểm đó. Đó là một năm trước, và điều này bây giờ hoạt động đáng tin cậy ngay cả trong Safari.

Tôi đã chỉnh sửa câu trả lời ban đầu của mình, ngoại trừ cảm thấy sai, vì hiện tại có nhiều người đã bỏ phiếu cho giải pháp thay đổi trong câu trả lời đó so với bản gốc. Tôi tin rằng đây là câu trả lời tốt hơn, và tôi hy vọng bạn đồng ý.


10
bạn nên sử dụng ===thay vì ==?
Neil S

12
Điều này cũng sẽ thất bại đối với những lời hứa không cùng cảnh giới.
Benjamin Gruenbaum

4
"một lời hứa theo định nghĩa của thông số kỹ thuật" dường như có nghĩa là "một lời hứa được tạo bởi cùng một nhà xây dựng như một lời hứa được tạo thông qua Promise.resolve () sẽ" - vì vậy điều này sẽ không phát hiện ra nếu vd. một lời hứa đầy đủ thực sự là một lời hứa
VoxPelli

3
Câu trả lời này có thể được cải thiện nếu nó bắt đầu bằng cách nêu rõ cách bạn diễn giải câu hỏi thay vì bắt đầu bằng một câu trả lời ngay lập tức-- OP rất tiếc đã không làm cho nó rõ ràng, và tại thời điểm này bạn cũng không OP, người viết và người đọc có khả năng trên 3 trang khác nhau. Tài liệu mà bạn đề cập nói "nếu đối số là một lời hứa được tạo bởi nhà xây dựng này ", phần in nghiêng là rất quan trọng. Sẽ là tốt để nói rằng đó là câu hỏi bạn đang trả lời. Ngoài ra, câu trả lời của bạn rất hữu ích cho người dùng thư viện này nhưng không phải là người triển khai.
Don nở

1
Đừng sử dụng phương pháp này, đây là lý do tại sao, nhiều hơn với quan điểm của @ BenjaminGruenbaum. gist.github.com/reggi/a1da4d0ea4f1320fa15405fb86353cff
ThomasReggi

61

Cập nhật: Đây không còn là câu trả lời tốt nhất. Thay vào đó hãy bỏ phiếu cho câu trả lời khác của tôi .

obj instanceof Promise

Hãy làm nó. Lưu ý rằng điều này chỉ có thể hoạt động đáng tin cậy với các lời hứa es6 bản địa.

Nếu bạn đang sử dụng shim, thư viện lời hứa hoặc bất cứ điều gì khác giả vờ giống như lời hứa, thì có thể thích hợp hơn để kiểm tra "có thể" (bất cứ điều gì có .thenphương pháp), như được hiển thị trong các câu trả lời khác ở đây.


Nó đã được chỉ ra cho tôi rằng Promise.resolve(obj) == objsẽ không hoạt động trong Safari. Sử dụng instanceof Promisethay thế.
jib

2
Điều này không hoạt động đáng tin cậy và gây ra cho tôi một vấn đề cực kỳ khó theo dõi. Giả sử bạn có một thư viện sử dụng shim es6.promise và bạn sử dụng Bluebird ở đâu đó, bạn sẽ gặp vấn đề. Vấn đề này xảy ra với tôi trong Chrome Canary.
vaughan

1
Vâng, câu trả lời này thực sự sai. Tôi đã kết thúc ở đây cho chính xác một vấn đề khó theo dõi. obj && typeof obj.then == 'function'Thay vào đó, bạn thực sự nên kiểm tra , bởi vì nó sẽ hoạt động với tất cả các loại lời hứa và thực sự là cách được đề xuất bởi thông số kỹ thuật và được sử dụng bởi các triển khai / polyfill. Promise.allVí dụ bản địa sẽ hoạt động trên tất cả các thenables, không chỉ các lời hứa bản địa khác. Mã của bạn cũng vậy. Vì vậy, instanceof Promisekhông phải là một giải pháp tốt.
Stijn de Witt

2
Theo dõi - điều tồi tệ hơn: Trên node.js 6.2.2 chỉ sử dụng các lời hứa riêng, hiện tại tôi đang cố gắng gỡ lỗi một vấn đề trong đó console.log(typeof p, p, p instanceof Promise);tạo ra kết quả đầu ra này : object Promise { <pending> } false. Như bạn có thể thấy đó là một lời hứa ổn - nhưng instanceof Promisebài kiểm tra có trở lại falsekhông?
Mörre

2
Điều này sẽ thất bại cho những lời hứa không cùng cảnh giới.
Benjamin Gruenbaum

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

6
Điều gì xảy ra nếu không xác định được? bạn cần đề phòng điều đó thông qua sự việc && ...
mrBorna

không tốt nhất nhưng chắc chắn rất có khả năng; cũng phụ thuộc vào phạm vi của vấn đề. Viết 100% phòng thủ thường được áp dụng trong các API công khai kết thúc mở hoặc nơi bạn biết hình dạng / chữ ký của dữ liệu là hoàn toàn mở.
rob2d

17

Để xem đối tượng đã cho có phải là Lời hứa ES6 không , chúng ta có thể sử dụng vị từ này:

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

Calling toStringtrực tiếp từ Object.prototypetrả về một biểu diễn chuỗi gốc của loại đối tượng đã cho "[object Promise]"trong trường hợp của chúng ta. Điều này đảm bảo rằng đối tượng đã cho

  • Bỏ qua các kết quả dương tính giả như ..:
    • Loại đối tượng tự xác định với cùng tên của hàm tạo ("Promise").
    • toStringPhương pháp tự viết của đối tượng đã cho.
  • Hoạt động trên nhiều bối cảnh môi trường (ví dụ iframe) tương phản vớiinstanceof hoặc isPrototypeOf.

Tuy nhiên, bất kỳ đối tượng máy chủ cụ thể nào có thẻ được sửa đổi thông quaSymbol.toStringTag đều có thể trả về "[object Promise]". Đây có thể là kết quả dự định hoặc không phụ thuộc vào dự án (ví dụ: nếu có triển khai Promise tùy chỉnh).


Để xem đối tượng đến từ Lời hứa ES6 bản địa , chúng ta có thể sử dụng:

function isNativePromise(p) {
  return p && typeof p.constructor === "function"
    && Function.prototype.toString.call(p.constructor).replace(/\(.*\)/, "()")
    === Function.prototype.toString.call(/*native object*/Function)
      .replace("Function", "Promise") // replacing Identifier
      .replace(/\(.*\)/, "()"); // removing possible FormalParameterList 
}

Theo phần nàyphần này của thông số kỹ thuật, biểu diễn chuỗi của hàm phải là:

" Định danh hàm ( optParameterList opt ) { FunctionBody }"

được xử lý phù hợp ở trên. Các FunctionBody[native code]trong tất cả các trình duyệt chính.

MDN: Function.prototype.toString

Điều này hoạt động trên nhiều bối cảnh môi trường là tốt.


11

Không phải là một câu trả lời cho câu hỏi đầy đủ nhưng tôi nghĩ rằng đáng để đề cập rằng trong Node.js 10, một hàm tiện ích mới được gọi isPromiseđã được thêm vào để kiểm tra xem một đối tượng có phải là Promise gốc hay không:

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

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

11

Đây là cách gói graphql-js phát hiện lời hứa:

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

valuelà giá trị trả về của hàm của bạn. Tôi đang sử dụng mã này trong dự án của tôi và không có vấn đề gì cho đến nay.


6

Đây là mẫu mã https://github.com/ssnau/xkit/blob/master/util/is-promise.js

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

nếu một đối tượng với một thenphương thức, nó sẽ được coi là a Promise.


3
Tại sao chúng ta cần obj === 'hàm' điều kiện btw?
Alendorff

Giống như câu trả lời này , bất kỳ đối tượng nào cũng có thể có phương thức "sau đó" và do đó không thể luôn được coi là một lời hứa.
Boghyon Hoffmann 27/03/18

6

Trong trường hợp bạn đang sử dụng nguyên cảo , tôi muốn thêm rằng bạn có thể sử dụng "loại ngữ" tính năng. Chỉ cần bọc xác minh logic trong một hàm trả về x is Promise<any>và bạn sẽ không cần phải thực hiện các dự báo. Dưới đây trong ví dụ của tôi, clà một lời hứa hoặc một trong những loại mà tôi muốn chuyển đổi thành một lời hứa bằng cách gọi c.fetch()phương thức.

export function toPromise(c: Container<any> | Promise<any>): Promise<any> {
    if (c == null) return Promise.resolve();
    return isContainer(c) ? c.fetch() : c;
}

export function isContainer(val: Container<any> | Promise<any>): val is Container<any> {
    return val && (<Container<any>>val).fetch !== undefined;
}

export function isPromise(val: Container<any> | Promise<any>): val is Promise<any> {
    return val && (<Promise<any>>val).then !== undefined;
}

Thông tin thêm: https://www.typescriptlang.org/docs/handbook/advified-types.html


6

Nếu bạn đang ở trong một phương thức không đồng bộ, bạn có thể làm điều này và tránh bất kỳ sự mơ hồ nào.

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

Nếu hàm trả về lời hứa, nó sẽ chờ và trả về với giá trị đã giải quyết. Nếu hàm trả về một giá trị, nó sẽ được coi là đã giải quyết.

Nếu chức năng không trả lại lời hứa hôm nay, nhưng ngày mai trả về một hoặc được tuyên bố là không đồng bộ, bạn sẽ là bằng chứng trong tương lai.


điều này hoạt động, theo ở đây : "nếu giá trị [được chờ đợi] không phải là một lời hứa, [biểu thức chờ đợi] chuyển đổi giá trị thành một Lời hứa đã được giải quyết và chờ đợi nó"
pqnet

Về cơ bản, đó là những gì đã được đề xuất trong câu trả lời được chấp nhận ngoại trừ ở đây cú pháp async-await được sử dụng thay vìPromise.resolve()
B12Toaster

3
it('should return a promise', function() {
    var result = testedFunctionThatReturnsPromise();
    expect(result).toBeDefined();
    // 3 slightly different ways of verifying a promise
    expect(typeof result.then).toBe('function');
    expect(result instanceof Promise).toBe(true);
    expect(result).toBe(Promise.resolve(result));
});

2

Tôi sử dụng chức năng này như một giải pháp phổ quát:

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

-1

Sau khi tìm kiếm một cách đáng tin cậy để phát hiện các chức năng Async hoặc thậm chí cả Promise , tôi đã kết thúc bằng thử nghiệm sau:

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

nếu bạn phân lớp Promisevà tạo các thể hiện của điều đó, thử nghiệm này có thể thất bại. điều này sẽ làm việc cho hầu hết những gì bạn đang cố gắng thử nghiệm mặc dù.
vào

Đồng ý, nhưng tôi không hiểu tại sao mọi người sẽ tạo ra những lời hứa hẹn
Sebastien H.

fn.constructor.name === 'AsyncFunction'là sai - điều đó có nghĩa là một cái gì đó là chức năng không đồng bộ và không phải là một lời hứa - cũng không được đảm bảo để hoạt động vì mọi người có thể phân lớp lời hứa
Benjamin Gruenbaum

@BenjaminGruenbaum Ví dụ trên hoạt động trong hầu hết các trường hợp, nếu bạn tạo lớp con của riêng mình, bạn nên thêm các bài kiểm tra vào tên của nó
Sebastien H.

Bạn có thể, nhưng nếu bạn đã biết những đối tượng nào thì bạn đã biết liệu công cụ có hứa hẹn hay không.
Benjamin Gruenbaum

-3

ES6:

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

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

2
Bất kỳ đối tượng nào có (hoặc đã ghi đè) toStringphương thức chỉ có thể trả về một chuỗi bao gồm "Promise".
Boghyon Hoffmann

4
Câu trả lời này rất tệ vì nhiều lý do, rõ ràng nhất là'NotAPromise'.toString().includes('Promise') === true
chết tiệt
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.