ฉันจะบอกได้อย่างไรว่าวัตถุคือสัญญา?


336

ไม่ว่าจะเป็นสัญญา ES6 หรือสัญญา Bluebird สัญญา Q ฯลฯ

ฉันจะทดสอบเพื่อดูว่าวัตถุที่ระบุเป็นสัญญาได้อย่างไร


3
อย่างดีที่สุดคุณสามารถตรวจสอบ.thenวิธีการได้ แต่นั่นจะไม่บอกคุณว่าสิ่งที่คุณมีคือสัญญาอย่างแน่นอน สิ่งที่คุณจะรู้ ณ จุดนั้นคือคุณมีสิ่งที่แสดง.thenวิธีการเช่นสัญญา
Scott Offen

@ScottOffen ข้อกำหนดของสัญญาอย่างชัดเจนไม่ได้ทำให้แตกต่าง
Benjamin Gruenbaum

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

3
@ScottOffen ตามคำจำกัดความวิธีเดียวที่กำหนดไว้เพื่อระบุสัญญาคือการตรวจสอบว่ามี.thenวิธีการหรือไม่ ใช่มันมีศักยภาพสำหรับการบวกเท็จ แต่มันก็เป็นข้อสันนิษฐานที่ทุกคนสัญญาว่าห้องสมุดพึ่งพา (เพราะนั่นคือทั้งหมดที่พวกเขาสามารถพึ่งพาได้) ทางเลือกเดียวที่ฉันเห็นได้คือการรับข้อเสนอแนะของ Benjamin Gruenbaum และเรียกใช้ผ่านชุดทดสอบสัญญา แต่นั่นไม่ได้เป็นจริงสำหรับรหัสการผลิตจริง
JLRishe

คำตอบ:


342

วิธีการตัดสินใจของห้องสมุดสัญญา

ถ้ามันมี.thenฟังก์ชั่น - นั่นเป็นเพียงห้องสมุดสัญญามาตรฐานเท่านั้นที่ใช้

ข้อมูลจำเพาะสัญญา / A + มีความคิดที่เรียกว่าthenซึ่งโดยพื้นฐานแล้วคือ "วัตถุที่มีthenวิธีการ" สัญญาจะและควรดูดซึมสิ่งใดด้วยวิธีการนั้น การดำเนินการตามสัญญาทั้งหมดที่คุณกล่าวถึงทำเช่นนี้

ถ้าเราดูข้อมูลจำเพาะ :

2.3.3.3 ถ้าthenเป็นฟังก์ชั่นให้เรียกมันด้วย x ว่านี่คืออาร์กิวเมนต์แรก

นอกจากนี้ยังอธิบายถึงเหตุผลสำหรับการตัดสินใจออกแบบนี้:

การรักษานี้thenAbles ช่วยให้การใช้งานที่สัญญาว่าจะทำงานร่วมกันได้ตราบใดที่พวกเขาเปิดเผยสัญญา / A + เข้ากันthenวิธีการ นอกจากนี้ยังอนุญาตให้การใช้งานสัญญา / A + สามารถใช้งาน“ ดูดซับ” การดำเนินการที่ไม่เป็นไปตามข้อกำหนดด้วยวิธีการที่สมเหตุสมผลแล้ว

คุณควรตัดสินใจอย่างไร

คุณไม่ควร - แทนโทรPromise.resolve(x)( Q(x)ใน Q) ที่จะเสมอแปลงค่าใด ๆ หรือภายนอกthenสามารถเข้าไปในสัญญาที่เชื่อถือได้ ปลอดภัยกว่าและง่ายกว่าการตรวจสอบด้วยตนเอง

จริงๆต้องให้แน่ใจ?

คุณสามารถเรียกใช้ผ่านชุดทดสอบ : D


168

ตรวจสอบว่าสิ่งที่สัญญาว่าจะซับซ้อนรหัสโดยไม่จำเป็นเพียงแค่ใช้ Promise.resolve

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

})

1
ดังนั้น Promise.resolve สามารถจัดการอะไรที่มาพร้อมวิธีของตนหรือไม่ ไม่อะไรเลย แต่ฉันเดาว่าอะไรจะสมเหตุสมผล
Alexander Mills

3
@AlexMills ใช่มันใช้งานได้กับสัญญาที่ไม่ได้มาตรฐานเช่นสัญญา jQuery มันอาจล้มเหลวหากวัตถุนั้นมีวิธีการที่มีส่วนต่อประสานที่แตกต่างไปจากสัญญาอย่างสิ้นเชิง
Esailija

19
คำตอบนี้แม้ว่าอาจเป็นคำแนะนำที่ดี แต่ก็ไม่ได้ตอบคำถาม
Stijn de Witt

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

4
@Esalija คำถามดูเหมือนว่าฉันจะมีความเกี่ยวข้องและมีความสำคัญไม่เพียง แต่เป็นผู้ดำเนินการของห้องสมุดสัญญา นอกจากนี้ยังเกี่ยวข้องกับผู้ใช้ห้องสมุดสัญญาที่ต้องการทราบว่าการใช้งานจะ / ควร / อาจทำงานอย่างไรและห้องสมุดสัญญาที่แตกต่างกันจะมีปฏิสัมพันธ์ซึ่งกันและกันได้อย่างไร โดยเฉพาะอย่างยิ่งผู้ใช้รายนี้รู้สึกผิดหวังอย่างมากจากความจริงที่ชัดเจนว่าฉันสามารถให้สัญญากับ X สำหรับ X ใด ๆ ยกเว้นเมื่อ X คือ "สัญญา" (อะไรก็ตาม "สัญญา" หมายถึงที่นี่ - นั่นคือคำถาม) และฉันสนใจแน่นอน ในการรู้ว่าขอบเขตของข้อยกเว้นนั้นอยู่ตรงไหน
Don Hatch

104

นี่คือคำตอบดั้งเดิมของฉันซึ่งได้รับการยอมรับในสเป็คเป็นวิธีการทดสอบสำหรับสัญญา:

Promise.resolve(obj) == obj

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

ฉันมีคำตอบอื่นที่นี่ซึ่งเคยพูดแบบนี้ แต่ฉันเปลี่ยนเป็นอย่างอื่นเมื่อมันไม่ได้ทำงานกับ Safari ในเวลานั้น นั่นเป็นปีที่แล้วและตอนนี้ทำงานได้อย่างน่าเชื่อถือแม้ใน Safari

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


10
คุณควรใช้===แทน==?
Neil S

12
สิ่งนี้จะล้มเหลวเพราะคำสัญญาที่ไม่ได้อยู่ในอาณาจักรเดียวกัน
Benjamin Gruenbaum

4
"สัญญาโดยนิยามของ spec" ดูเหมือนจะหมายถึง "สัญญาที่สร้างโดยนวกรรมิกเดียวกันกับสัญญาที่สร้างขึ้นโดย Promise.resolve () จะเป็น" - ดังนั้นสิ่งนี้จะล้มเหลวในการตรวจสอบว่าเช่น สัญญาโพลีฟิลเป็นสัญญาจริง
VoxPelli

3
คำตอบนี้อาจปรับปรุงได้ถ้ามันจะเริ่มต้นด้วยการระบุว่าคุณตีความคำถามแทนที่จะเริ่มต้นด้วยคำตอบทันที - OP น่าเสียดายที่ไม่ได้ทำให้ชัดเจนและคุณยังไม่ได้ดังนั้น ณ จุดนี้ OP นักเขียนและผู้อ่านมีแนวโน้มใน 3 หน้าที่แตกต่างกัน เอกสารที่คุณอ้างถึงพูดว่า "ถ้าการโต้เถียงเป็นสัญญาที่สร้างโดยผู้สร้างนี้ " ส่วนที่เอียงนั้นสำคัญมาก เป็นการดีที่จะระบุว่าเป็นคำถามที่คุณตอบ อีกทั้งคำตอบของคุณก็มีประโยชน์สำหรับผู้ใช้ของห้องสมุดนี้ แต่ไม่ใช่ผู้ดำเนินการ
Don Hatch

1
อย่าใช้วิธีนี้นี่คือเหตุผลทำไมถึงประเด็นของ @ BenjaminGruenbaum gist.github.com/reggi/a1da4d0ea4f1320fa15405fb86358cff
ThomasReggi

61

อัปเดต:นี่ไม่ใช่คำตอบที่ดีที่สุดอีกต่อไป โปรดโหวตคำตอบอื่น ๆ ของฉันแทน

obj instanceof Promise

ควรทำมัน โปรดทราบว่าสิ่งนี้อาจทำงานได้อย่างน่าเชื่อถือกับสัญญา es6 แบบดั้งเดิมเท่านั้น

หากคุณใช้ shim, ห้องสมุดสัญญาหรือสิ่งอื่นใดที่อ้างว่าเป็นเหมือนสัญญาคุณควรทดสอบคำว่า "แล้ว" (อะไรก็ได้ที่มี.thenวิธีการ) ดังที่แสดงไว้ในคำตอบอื่น ๆ ที่นี่


มันได้รับการชี้ให้เห็นว่าฉันPromise.resolve(obj) == objจะไม่ทำงานใน Safari ใช้instanceof Promiseแทน
jib

2
สิ่งนี้ใช้งานไม่ได้อย่างน่าเชื่อถือและเป็นสาเหตุให้ฉันติดตามปัญหาได้อย่างบ้าคลั่ง สมมติว่าคุณมีห้องสมุดที่ใช้ es6.promise shim และคุณใช้ Bluebird ที่ไหนสักแห่งคุณจะมีปัญหา ปัญหานี้เกิดขึ้นกับฉันใน Chrome Canary
vaughan

1
ใช่คำตอบนี้ผิดจริงๆ ฉันลงเอยที่นี่เพื่อแก้ไขปัญหาที่ยากมาก คุณควรตรวจสอบobj && typeof obj.then == 'function'แทนเพราะจะใช้ได้กับทุกประเภทของสัญญาและเป็นวิธีที่แนะนำโดย spec และใช้โดย implementations / polyfills Promise.allตัวอย่างเช่นชาวพื้นเมืองจะทำงานในthenables ทั้งหมดไม่เพียง แต่สัญญาพื้นเมืองอื่น ๆ ดังนั้นรหัสของคุณควร ดังนั้นจึงinstanceof Promiseไม่ใช่ทางออกที่ดี
Stijn de Witt

2
ติดตาม - มันเลวร้ายยิ่ง: ใน Node.js 6.2.2 ใช้เฉพาะสัญญาพื้นเมืองของฉันตอนนี้พยายามที่จะแก้ปัญหาปัญหาที่ผลิตออกนี้:console.log(typeof p, p, p instanceof Promise); object Promise { <pending> } falseอย่างที่คุณเห็นมันเป็นสัญญาที่ดี - แต่การinstanceof Promiseทดสอบก็กลับมาfalse?
Mörre

2
สิ่งนี้จะล้มเหลวเพราะคำสัญญาที่ไม่ได้อยู่ในอาณาจักรเดียวกัน
Benjamin Gruenbaum

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

6
เกิดอะไรขึ้นถ้าสิ่งที่ไม่ได้กำหนด? คุณต้องป้องกันสิ่งนั้นผ่านสิ่ง && ...
mrBorna

ไม่ดีที่สุด แต่มีแนวโน้มมาก; ขึ้นอยู่กับขอบเขตของปัญหาด้วย การป้องกันแบบ 100% นั้นมักจะใช้กับ API สาธารณะที่สิ้นสุดเปิดหรือที่คุณทราบว่ารูปร่าง / ลายเซ็นของข้อมูลนั้นเป็นแบบเปิดสมบูรณ์
rob2d

17

เพื่อดูว่าวัตถุที่ให้ไว้เป็นสัญญา ES6เราสามารถใช้ภาคแสดงนี้ได้หรือไม่:

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

Callไอเอ็นจีtoStringโดยตรงจากObject.prototypeผลตอบแทนที่เป็นตัวแทนสตริงพื้นเมืองของประเภทวัตถุที่กำหนดซึ่งเป็น"[object Promise]"ในกรณีของเรา สิ่งนี้ทำให้มั่นใจได้ว่าวัตถุที่กำหนด

  • ข้ามบวกเท็จเช่น .. :
    • ประเภทวัตถุที่กำหนดเองที่มีชื่อตัวสร้างเดียวกัน ("สัญญา")
    • toStringวิธีการเขียนด้วยตนเองของวัตถุที่กำหนด
  • สามารถทำงานได้ทั้งบริบทสภาพแวดล้อมหลาย ๆ (เช่น iframes) ในทางตรงกันข้ามกับinstanceofisPrototypeOfหรือ

อย่างไรก็ตามวัตถุโฮสต์ใด ๆที่มีการแก้ไขแท็กผ่านSymbol.toStringTagสามารถส่งคืน"[object Promise]"ได้ นี่อาจเป็นผลลัพธ์ที่ตั้งใจหรือไม่ขึ้นอยู่กับโครงการ (เช่นหากมีการใช้งานสัญญาแบบกำหนดเอง)


เพื่อดูว่าวัตถุนั้นมาจากสัญญา ES6 แบบดั้งเดิมเราสามารถใช้:

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 
}

ตามนี้และส่วนนี้ของสเปคที่เป็นตัวแทนสตริงของฟังก์ชั่นที่ควรจะเป็น:

"ฟังก์ชั่นตัวระบุ (การเลือก FormalParameterList ) { FunctionBody }"

ซึ่งจัดการตามข้างต้น FunctionBodyเป็น[native code]เบราว์เซอร์ที่สำคัญทั้งหมด

MDN: Function.prototype.toString

สิ่งนี้สามารถทำงานได้ในสภาพแวดล้อมที่หลากหลายเช่นกัน


12

ไม่ใช่คำตอบสำหรับคำถามเต็ม แต่ฉันคิดว่ามันมีค่าที่จะกล่าวถึงใน Node.js 10 ฟังก์ชั่น util ใหม่ที่เรียกว่าisPromiseถูกเพิ่มเข้ามาซึ่งจะตรวจสอบว่าวัตถุนั้นเป็น Native Promise หรือไม่:

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

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

11

นี่คือวิธีที่แพ็คเกจ Graphql-jsตรวจพบสัญญา:

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

valueคือค่าที่ส่งคืนของฟังก์ชันของคุณ ฉันใช้รหัสนี้ในโครงการของฉันและไม่มีปัญหาจนถึงตอนนี้


6

นี่คือรูปแบบรหัสhttps://github.com/ssnau/xkit/blob/master/util/is-promise.js

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

ถ้าวัตถุมีวิธีการก็ควรจะรักษาเป็นthenPromise


3
ทำไมเราต้องการ obj === 'ฟังก์ชั่น' เงื่อนไข btw?
Alendorff

เช่นเดียวกับคำตอบนี้วัตถุใด ๆ สามารถมีวิธีการ "แล้ว" และดังนั้นจึงไม่สามารถได้รับการปฏิบัติตามสัญญา
Boghyon Hoffmann

6

ในกรณีที่คุณใช้typescriptฉันต้องการเพิ่มว่าคุณสามารถใช้คุณสมบัติ "พิมพ์ภาค" เพียง แต่ควรรวมการตรวจสอบตรรกะในฟังก์ชันที่ส่งคืนx is Promise<any>และคุณไม่จำเป็นต้องพิมพ์ตัวอักษร ด้านล่างในตัวอย่างของฉันcเป็นสัญญาหรือหนึ่งในประเภทของฉันที่ฉันต้องการแปลงเป็นสัญญาโดยเรียกc.fetch()วิธีการ

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

ข้อมูลเพิ่มเติม: https://www.typescriptlang.org/docs/handbook/advanced-types.html


6

หากคุณอยู่ในวิธีการซิงค์คุณสามารถทำได้และหลีกเลี่ยงความคลุมเครือใด ๆ

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

หากฟังก์ชันส่งคืนสัญญาจะรอและส่งคืนพร้อมค่าที่แก้ไข หากฟังก์ชั่นส่งกลับค่ามันจะได้รับการปฏิบัติตามที่ได้รับการแก้ไข

หากฟังก์ชันไม่ส่งคืนสัญญาวันนี้ แต่พรุ่งนี้ส่งคืนหนึ่งสัญญาหรือมีการประกาศเป็นอะซิงก์คุณจะได้รับการพิสูจน์ในอนาคต


งานนี้ตามที่นี่ : "ถ้าค่า [ที่รอคอย] ไม่ใช่สัญญา [นิพจน์ที่รอ] จะแปลงค่าเป็นสัญญาที่ได้รับการแก้ไขแล้วและรอให้มัน"
pqnet

มันเป็นสิ่งที่ได้รับการแนะนำในคำตอบที่ได้รับการยอมรับยกเว้นที่นี่จะใช้ไวยากรณ์แบบรอคอยแบบ async แทน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

ฉันใช้ฟังก์ชั่นนี้เป็นวิธีแก้ปัญหาสากล:

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

-1

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

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

หากคุณคลาสย่อยPromiseและสร้างอินสแตนซ์ของการทดสอบนี้อาจล้มเหลว สิ่งนี้น่าจะใช้ได้ผลกับสิ่งที่คุณพยายามจะทดสอบ
theram

เห็นด้วย แต่ฉันไม่เห็นว่าทำไมทุกคนจะสร้าง sublasses ของคำสัญญา
Sebastien H.

fn.constructor.name === 'AsyncFunction'เป็นสิ่งที่ผิด - มันหมายถึงบางสิ่งบางอย่างเป็นฟังก์ชั่นแบบอะซิงก์และไม่ใช่สัญญา - นอกจากนี้ยังไม่รับประกันว่าจะทำงานได้เพราะผู้คนสามารถทำสัญญา subclass ได้
Benjamin Gruenbaum

@BenjaminGruenbaum ตัวอย่างข้างต้นใช้งานได้ในกรณีส่วนใหญ่หากคุณสร้างคลาสย่อยของคุณเองคุณควรเพิ่มการทดสอบในชื่อ
Sebastien H.

คุณสามารถ แต่ถ้าคุณรู้อยู่แล้วว่ามีวัตถุอะไรอยู่คุณก็รู้อยู่แล้วว่าสิ่งของนั้นมีสัญญาหรือไม่
Benjamin Gruenbaum

-3

ES6:

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

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

2
วัตถุใด ๆ ที่มี (หรือเขียนทับได้) วิธีการก็สามารถกลับสตริงที่มีtoString "Promise"
Boghyon Hoffmann

4
คำตอบนี้ไม่ถูกต้องด้วยเหตุผลหลายประการสิ่งที่ชัดเจนที่สุด'NotAPromise'.toString().includes('Promise') === true
damd
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.