ตำแหน่งของการจับก่อนและหลังนั้น


109

ฉันมีปัญหาในการทำความเข้าใจความแตกต่างระหว่างการใส่.catchก่อนและหลังในสัญญาซ้อนกัน

ทางเลือกที่ 1:

test1Async(10).then((res) => {
  return test2Async(22)
    .then((res) => {
      return test3Async(100);
    }).catch((err) => {
      throw "ERROR AFTER THEN";
    });
}).then((res) => {
  console.log(res);
}).catch((err) => {
  console.log(err);
});

ทางเลือกที่ 2:

test1Async(10).then((res) => {
   return test2Async(22)
     .catch((err) => {
        throw "ERROR BEFORE THEN";
      })
      .then((res) => {
        return test3Async(100);
      });
  }).then((res) => {
    console.log(res);
  }).catch((err) => {
    console.log(err);
  });

พฤติกรรมของแต่ละฟังก์ชั่นมีดังนี้, test1 ล้มเหลวถ้าหมายเลข<0test2 ล้มเหลวถ้าตัวเลข> 10และ test3 100ล้มเหลวหากจำนวนไม่ได้ ในกรณีนี้ test2 ล้มเหลวเท่านั้น

ฉันพยายามรันและทำให้ test2Async ล้มเหลวทั้งก่อนและหลังจากนั้นจะทำงานในลักษณะเดียวกันและนั่นไม่ได้ดำเนินการ test3Async ใครช่วยอธิบายความแตกต่างหลักของการวางที่จับในที่ต่างๆให้ฉันได้ไหม

ในแต่ละฟังก์ชั่นฉันconsole.log('Running test X')เพื่อตรวจสอบว่าได้รับการดำเนินการหรือไม่

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


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

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

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

คุณหมายถึงอะไร "การเปลี่ยนแปลงผลลัพธ์" ขออภัยฉันสับสนจริงๆฮ่าฮ่า
Zanko

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

คำตอบ:


249

โดยพื้นฐานแล้วคุณกำลังถามว่าอะไรคือความแตกต่างระหว่างสองสิ่งนี้ ( pคำสัญญาที่สร้างขึ้นจากรหัสก่อนหน้านี้อยู่ที่ไหน):

return p.then(...).catch(...);

และ

return p.catch(...).then(...);

มีความแตกต่างไม่ว่าจะเมื่อ p แก้ไขหรือปฏิเสธ แต่ความแตกต่างเหล่านั้นมีความสำคัญหรือไม่ขึ้นอยู่กับสิ่งที่โค้ดภายใน.then()หรือ.catch()ตัวจัดการทำ

จะเกิดอะไรขึ้นเมื่อpแก้ไข:

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

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

นั่นคือความแตกต่าง # 1 หาก.catch()ตัวจัดการเป็น AFTER ก็สามารถตรวจจับข้อผิดพลาดภายใน.then()ตัวจัดการได้เช่นกัน

จะเกิดอะไรขึ้นเมื่อถูกpปฏิเสธ:

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

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

นั่นคือความแตกต่าง # 2 หาก.catch()ตัวจัดการอยู่ก่อนจะสามารถจัดการข้อผิดพลาดและอนุญาตให้.then()ตัวจัดการยังคงถูกเรียก

ควรใช้เมื่อใด:

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

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

ตัวเลือกอื่น ๆ

มีอีกหนึ่งทางเลือกในการใช้การโทรกลับทั้งสองแบบที่คุณสามารถส่งผ่านได้.then()ใน:

 p.then(fn1, fn2)

สิ่งนี้รับประกันได้ว่าจะมีเพียงหนึ่งในfn1หรือfn2จะถูกเรียก ถ้าpแก้ไขได้ก็fn1จะเรียก. ถ้าpปฏิเสธก็fn2จะเรียก ไม่มีการเปลี่ยนแปลงของผลลัพธ์ที่fn1สามารถfn2เรียกได้หรือในทางกลับกัน ดังนั้นหากคุณต้องการตรวจสอบให้แน่ใจว่ามีการเรียกตัวจัดการเพียงหนึ่งในสองตัวของคุณไม่ว่าจะเกิดอะไรขึ้นในตัวจัดการเองคุณก็สามารถp.then(fn1, fn2)ใช้ได้


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

9
@ArneHugo - ข้อเสนอแนะที่ดี ฉันเพิ่ม
jfriend00

ดังนั้นในระหว่าง Promise Chaining เราสามารถเขียน. แล้ว. จับ. จับสถานการณ์แบบนั้นได้หรือไม่?
Kapil Raghuwanshi

@KapilRaghuwanshi ใช่คุณสามารถใช้เพื่อส่งผ่านค่าเริ่มต้นในกรณีที่ล้มเหลว ie Promise.reject(new Error("F")).then(x => x).catch(e => {console.log(e); return [1]}).then(console.log)และ Promise.resolve([2]).then(x => x).catch(e => [1]).then(console.log)
CervEd

1
@DmitryShvedov - .then(this.setState({isModalOpen: false}))ขณะที่ผมเดานี้ไม่ถูกต้อง คุณไม่ได้ส่งผ่านการอ้างอิงฟังก์ชันเพื่อ.then()ให้โค้ดใน parens ถูกดำเนินการทันที (ก่อนที่คำสัญญาจะแก้ไข) .then(() => this.setState({isModalOpen: false}))มันควรจะเป็น
jfriend00

33

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

return p.then(...).catch(...);

คล้ายกับซิงโครนัส:

try {
  iMightThrow() // like `p`
  then()
} catch (err) {
  handleCatch()
}

ถ้าiMightThrow()ไม่โยนthen()จะถูกเรียก ถ้ามันโยน (หรือถ้าthen()ตัวมันเองขว้าง) ก็handleCatch()จะถูกเรียก สังเกตว่าcatchบล็อกไม่มีการควบคุมว่าจะthenถูกเรียกหรือไม่

ในทางกลับกัน,

return p.catch(...).then(...);

คล้ายกับซิงโครนัส:

try {
  iMightThrow()
} catch (err) {
  handleCatch()
}

then()

ในกรณีนี้ถ้าiMightThrow()ไม่โยนก็then()จะดำเนินการ หากมีการโยนก็ขึ้นอยู่กับhandleCatch()การตัดสินใจว่าthen()จะถูกเรียกหรือไม่เพราะหากมีการhandleCatch()ย้อนกลับthen()จะไม่ถูกเรียกเนื่องจากข้อยกเว้นจะถูกโยนไปยังผู้โทรทันที หากhandleCatch()สามารถจัดการปัญหาได้อย่างสง่างามก็then()จะเรียก


นี่เป็นคำอธิบายที่ดี แต่คุณสามารถห่อเด็กกำพร้าthen()ในfinally{...}
tyskr

2
@ 82Tuskers คุณแน่ใจหรือ ถ้าใส่then()เข้าไปfinally{...}จะไม่เรียกผิดแม้ว่าจะhandleCatch()ขว้าง? โปรดทราบว่าเป้าหมายของฉันคือการแสดงรหัสซิงโครนัสที่คล้ายคลึงกันไม่ใช่เพื่อแนะนำวิธีการจัดการข้อยกเว้นต่างๆ
akivajgordon

ดังนั้นหากเราต้องการจัดการทุกกรณี แต่ยังคง chain. then () จะดีที่สุดไหมที่จะใช้. then (do something) .catch (log err and update state). then (do another thing) .catch (log err) ที่เราพยายามจับทุกจุด แต่ยังดำเนินการตามขั้นตอนต่อไป?
anna
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.