เมื่อใดจึงจะถือว่าเป็นปฏิปักษ์ต่อคำสัญญา?


188

ฉันได้ดูที่สัญญาคำถามที่พบบ่อยนกชนิดหนึ่งในสิ่งที่มันกล่าวว่า.then(success, fail)เป็น antipattern ฉันไม่ค่อยเข้าใจคำอธิบายสำหรับลองและจับ เกิดอะไรขึ้นกับสิ่งต่อไปนี้

some_promise_call()
.then(function(res) { logger.log(res) }, function(err) { logger.log(err) })

ดูเหมือนว่าตัวอย่างจะแนะนำสิ่งต่อไปนี้เป็นวิธีที่ถูกต้อง

some_promise_call()
.then(function(res) { logger.log(res) })
.catch(function(err) { logger.log(err) })

ความแตกต่างคืออะไร?


1
then().catch()สามารถอ่านได้มากขึ้นเนื่องจากคุณไม่จำเป็นต้องค้นหาเครื่องหมายจุลภาคและตรวจสอบว่าการติดต่อกลับนี้เพื่อความสำเร็จหรือสาขาที่ล้มเหลว
Krzysztof Safjanowski

7
@KevinB: มีความแตกต่างกันมากตรวจสอบคำตอบ
Bergi

12
@KrzysztofSafjanowski - ทำลายโดยอาร์กิวเมนต์ 'ดูดีกว่า' ผิดอย่างสิ้นเชิง!
Andrey Popov

6
หมายเหตุ:เมื่อคุณใช้.catchงานอยู่คุณไม่ทราบว่าขั้นตอนใดที่ทำให้เกิดปัญหา - ภายในthenห่วงโซ่สุดท้ายหรือที่อื่น ๆ ดังนั้นมันจึงมีข้อเสียของตัวเอง
vitaly-t

2
ฉันมักจะเพิ่มชื่อฟังก์ชั่นให้กับสัญญาแล้ว. () params เพื่อให้สามารถอ่านได้คือsome_promise_call() .then(function fulfilled(res) { logger.log(res) }, function rejected(err) { logger.log(err) })
Shane Rowatt

คำตอบ:


215

ความแตกต่างคืออะไร?

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

นี่คือแผนภาพการควบคุมการไหล :

แผนภาพการไหลของการควบคุมนั้นมีสองข้อโต้แย้ง แผนภาพควบคุมการไหลของห่วงโซ่จับแล้ว

ในการแสดงมันในรหัสซิงโครนัส:

// some_promise_call().then(logger.log, logger.log)
then: {
    try {
        var results = some_call();
    } catch(e) {
        logger.log(e);
        break then;
    } // else
        logger.log(results);
}

ที่สองlog(ซึ่งเป็นเหมือนอาร์กิวเมนต์แรก.then()) จะถูกดำเนินการเฉพาะในกรณีที่ไม่มีข้อยกเว้นเกิดขึ้น บล็อกที่มีป้ายกำกับและbreakคำสั่งรู้สึกแปลก ๆ นี่เป็นสิ่งที่หลามมีtry-except-elseให้ (แนะนำให้อ่าน!)

// some_promise_call().then(logger.log).catch(logger.log)
try {
    var results = some_call();
    logger.log(results);
} catch(e) {
    logger.log(e);
}

ตัวcatchบันทึกจะจัดการข้อยกเว้นจากการเรียกตัวบันทึกสำเร็จด้วย

มากสำหรับความแตกต่าง

ฉันไม่ค่อยเข้าใจคำอธิบายสำหรับลองและจับ

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

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


เกิดอะไรขึ้นกับสิ่งต่อไปนี้

some_promise_call()
.then(function(res) { logger.log(res) }, function(err) { logger.log(err) })

ว่าคุณต้องโทรกลับซ้ำ คุณค่อนข้างต้องการ

some_promise_call()
   .catch(function(e) {
       return e; // it's OK, we'll just log it
   })
   .done(function(res) {
       logger.log(res);
   });

คุณอาจพิจารณาใช้.finally()สำหรับสิ่งนี้


7
นี่เป็นคำอธิบายที่เป็นประโยชน์ที่สุดที่ฉันได้อ่านในสองสามวัน (และฉันอ่านมาก) ฉันไม่สามารถอธิบายได้ว่าฉันรู้สึกขอบคุณแค่ไหน! :) ฉันคิดว่าคุณควรเน้นความแตกต่างระหว่างทั้งสองให้มากขึ้นซึ่ง.catchจะตรวจจับข้อผิดพลาดแม้ในฟังก์ชั่นความสำเร็จ .. โดยส่วนตัวแล้วฉันพบสิ่งผิดปกติอย่างยิ่งเมื่อคุณจบลงด้วยจุดเข้าผิดพลาดเพียงจุดเดียว หลายการกระทำ แต่นี่เป็นปัญหาของฉัน อย่างไรก็ตาม - ขอบคุณสำหรับข้อมูล! คุณไม่มีเครื่องมือสื่อสารออนไลน์ที่คุณต้องการแบ่งปันเพื่อให้ฉันสามารถถามเพิ่มเติมได้อีกหรือไม่ : P
Andrey Popov

2
ฉันหวังว่านี่จะให้ upvotes เพิ่มเติมที่นี่ หนึ่งในคำอธิบายที่ดีที่สุดของPromiseช่างสำคัญในเว็บไซต์นี้
Patrick Roberts

2
.done()ไม่ใช่ส่วนหนึ่งของมาตรฐานใช่หรือไม่ อย่างน้อย MDN ไม่ได้แสดงรายการวิธีการนั้น มันจะเป็นประโยชน์
ygoe

1
@ygoe แน่นอน doneเป็นสิ่ง Bluebird ที่ถูกปฏิเสธโดยทั่วไปthen+ การตรวจจับการปฏิเสธจัดการ
Bergi

1
เพียงทราบจากบอดสี A: แผนภาพทำให้รู้สึกไม่ :)
เบนนี่ K

37

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

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

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


18

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

จับวิธีการ

some_promise_call()
.then(function(res) { logger.log(res) })
.catch(function(err) { logger.log(err) })

ข้อดี

  1. ข้อผิดพลาดทั้งหมดได้รับการจัดการโดยหนึ่ง catch block
  2. แม้กระทั่งจับข้อยกเว้นใด ๆ ในบล็อกนั้น
  3. การผูกมัดการโทรกลับสำเร็จหลายครั้ง

ข้อเสีย

  1. ในกรณีของการผูกมัดมันเป็นการยากที่จะแสดงข้อความผิดพลาดที่แตกต่างกัน

วิธีการสำเร็จ / ผิดพลาด

some_promise_call()
.then(function success(res) { logger.log(res) },
      function error(err) { logger.log(err) })

ข้อดี

  1. คุณได้รับการควบคุมข้อผิดพลาดที่ละเอียด
  2. คุณสามารถมีฟังก์ชันการจัดการข้อผิดพลาดทั่วไปสำหรับข้อผิดพลาดประเภทต่าง ๆ เช่นข้อผิดพลาด db, 500 ข้อผิดพลาด ฯลฯ

Disavantages

  1. คุณจะยังคงต้องการอีกcatchหากคุณต้องการจัดการข้อผิดพลาดจากการโทรกลับสำเร็จ

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

คำถาม. ให้บอกว่าฉันโทร async ที่ทำสิ่งหนึ่งในสองสามสิ่ง: 1) ส่งคืนสำเร็จ (สถานะรหัส 2xx), 2) ส่งกลับไม่สำเร็จ (รหัส 4xx หรือ 5xx) แต่ไม่ปฏิเสธต่อ se, 3) หรือไม่ส่งคืนเลย ( การเชื่อมต่ออินเทอร์เน็ตไม่ทำงาน) สำหรับกรณีที่ # 1 การโทรกลับสำเร็จใน. แล้วจะได้รับผลกระทบ สำหรับกรณีที่ # 2 การเรียกกลับข้อผิดพลาดใน. แล้วถูกตี สำหรับกรณีที่ # 3, .catch ถูกเรียก นี่คือการวิเคราะห์ที่ถูกต้องใช่มั้ย กรณีที่ # 2 เป็น bc ที่ยุ่งยากที่สุดในทางเทคนิค 4xx หรือ 5xx ไม่ใช่การปฏิเสธ แต่ก็ยังส่งคืนได้สำเร็จ ดังนั้นเราต้องจัดการกับมันภายใน. จากนั้น .... ความเข้าใจของฉันถูกต้องหรือไม่
Benjamin Hoffman

"สำหรับกรณีที่ # 2 การเรียกกลับข้อผิดพลาดใน. แล้วจะถูกชนสำหรับกรณีที่ # 3 จะเรียก.. การจับนี้เป็นการวิเคราะห์ที่ถูกต้องใช่ไหม" - นั่นเป็นวิธีการดึงข้อมูล
aWebDeveloper

2

คำอธิบายอย่างง่าย:

ใน ES2018

เมื่อเมธอด catch ถูกเรียกด้วยอาร์กิวเมนต์ onRejected จะมีการดำเนินการขั้นตอนต่อไปนี้:

  1. ให้สัญญาเป็นค่านี้
  2. กลับมา เรียกใช้ (สัญญา "จากนั้น", «ไม่ได้กำหนด, on ถูกปฏิเสธ»)

นั่นหมายความว่า:

promise.then(f1).catch(f2)

เท่ากับ

promise.then(f1).then(undefiend, f2)

1

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


0

การใช้then()และcatch()ช่วยให้ประสบความสำเร็จในการจัดการความล้มเหลว ทำงานบนสัญญาส่งกลับโดยcatch() then()มันจัดการ

  1. หากสัญญาถูกปฏิเสธ ดู # 3 ในภาพ
  2. หากเกิดข้อผิดพลาดในตัวจัดการความสำเร็จของ then () ระหว่างหมายเลขบรรทัด 4 ถึง 7 ด้านล่าง ดู # 2.a ในภาพ (ความล้มเหลวในการโทรกลับthen()ไม่ได้จัดการสิ่งนี้)
  3. หากเกิดข้อผิดพลาดในตัวจัดการความล้มเหลวของ then () ให้หมายเลขบรรทัด 8 ด้านล่าง ดู # 3.b ในภาพ

1. let promiseRef: Promise = this. aTimetakingTask (false); 2. promiseRef 3. .then( 4. (result) => { 5. /* successfully, resolved promise. 6. Work on data here */ 7. }, 8. (error) => console.log(error) 9. ) 10. .catch( (e) => { 11. /* successfully, resolved promise. 12. Work on data here */ 13. });

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

หมายเหตุ : หลายครั้งตัวจัดการความล้มเหลวอาจไม่ได้กำหนดถ้าcatch()เขียนไว้แล้ว แก้ไข: reject()ส่งผลในการเรียกใช้catch()เฉพาะในกรณีที่ผิดพลาดในการจัดการthen()จะไม่ได้กำหนดไว้ แจ้งให้ทราบcatch()ล่วงหน้า# 3 ในภาพเพื่อ มันถูกเรียกใช้เมื่อตัวจัดการในบรรทัด # 8 และ 9 ไม่ได้ถูกกำหนดไว้

มันสมเหตุสมผลเนื่องจากสัญญาที่ส่งคืนโดยthen()ไม่มีข้อผิดพลาดหากมีการติดต่อกลับ


ลูกศรจากหมายเลข 3 ถึงหมายเลขcatchโทรกลับดูเหมือนว่าผิด
Bergi

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

-1

แทนที่จะเป็นคำพูดเป็นตัวอย่างที่ดี รหัสต่อไปนี้ (หากสัญญาแรกได้รับการแก้ไข):

Promise.resolve()
.then
(
  () => { throw new Error('Error occurs'); },
  err => console.log('This error is caught:', err)
);

เหมือนกับ:

Promise.resolve()
.catch
(
  err => console.log('This error is caught:', err)
)
.then
(
  () => { throw new Error('Error occurs'); }
)

แต่ด้วยการปฏิเสธสัญญาแรกสิ่งนี้ไม่เหมือนกัน:

Promise.reject()
.then
(
  () => { throw new Error('Error occurs'); },
  err => console.log('This error is caught:', err)
);

Promise.reject()
.catch
(
  err => console.log('This error is caught:', err)
)
.then
(
  () => { throw new Error('Error occurs'); }
)

4
ไม่เหมาะสมคุณช่วยลบคำตอบนี้ออกได้ไหม มันทำให้เข้าใจผิดและเบี่ยงเบนความสนใจจากคำตอบที่ถูกต้อง
Andy Ray

@AndyRay สิ่งนี้ไม่สมเหตุสมผลในการใช้งานจริง แต่มันก็สมเหตุสมผลที่จะเข้าใจการทำงานของสัญญา
ktretyak
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.