ฉันต้องส่งคืนหลังจากแก้ไข / ปฏิเสธก่อนกำหนดหรือไม่


262

สมมติว่าฉันมีรหัสต่อไปนี้

function divide(numerator, denominator) {
 return new Promise((resolve, reject) => {

  if(denominator === 0){
   reject("Cannot divide by 0");
   return; //superfluous?
  }

  resolve(numerator / denominator);

 });
}

หากเป้าหมายของฉันคือการใช้rejectเพื่อออกก่อนกำหนดฉันควรเข้าสู่นิสัยการreturnไอเอ็นจีทันทีหลังจากนั้นเช่นกัน?


5
ใช่เนื่องจากการดำเนินการจนเสร็จ

คำตอบ:


371

returnวัตถุประสงค์คือการยุติการดำเนินการของการทำงานหลังจากที่ปฏิเสธและป้องกันไม่ให้การดำเนินการของรหัสหลังจากที่มัน

function divide(numerator, denominator) {
  return new Promise((resolve, reject) => {

    if (denominator === 0) {
      reject("Cannot divide by 0");
      return; // The function execution ends here 
    }

    resolve(numerator / denominator);
  });
}

ในกรณีนี้จะป้องกันไม่ให้การresolve(numerator / denominator);ดำเนินการซึ่งไม่จำเป็นอย่างเคร่งครัด อย่างไรก็ตามยังคงดีกว่าที่จะยุติการดำเนินการเพื่อป้องกันกับดักที่เป็นไปได้ในอนาคต นอกจากนี้เป็นแนวปฏิบัติที่ดีในการป้องกันการเรียกใช้รหัสโดยไม่จำเป็น

พื้นหลัง

สัญญาสามารถอยู่ในหนึ่งใน 3 สถานะ:

  1. รอดำเนินการ - สถานะเริ่มต้น จากการรอดำเนินการเราสามารถย้ายไปยังสถานะอื่น
  2. ปฏิบัติ - สำเร็จ
  3. ปฏิเสธ - การดำเนินการล้มเหลว

เมื่อสัญญาเป็นจริงหรือปฏิเสธสัญญานั้นจะคงอยู่ในสถานะนี้ (ตัดสิน) ดังนั้นการปฏิเสธสัญญาที่ได้รับการเติมเต็มหรือการทำตามสัญญาที่ถูกปฏิเสธจะไม่มีผล

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

function divide(numerator, denominator) {
  return new Promise((resolve, reject) => {
    if (denominator === 0) {
      reject("Cannot divide by 0");
    }

    resolve(numerator / denominator);
  });
}

divide(5,0)
  .then((result) => console.log('result: ', result))
  .catch((error) => console.log('error: ', error));

แล้วทำไมเราต้องส่งคืน

แม้ว่าเราจะไม่สามารถเปลี่ยนสถานะสัญญาที่ชำระแล้วการปฏิเสธหรือการแก้ไขจะไม่หยุดการดำเนินการของฟังก์ชันที่เหลือ ฟังก์ชั่นอาจมีรหัสที่จะสร้างผลลัพธ์ที่สับสน ตัวอย่างเช่น:

function divide(numerator, denominator) {
  return new Promise((resolve, reject) => {
    if (denominator === 0) {
      reject("Cannot divide by 0");
    }
    
    console.log('operation succeeded');

    resolve(numerator / denominator);
  });
}

divide(5, 0)
  .then((result) => console.log('result: ', result))
  .catch((error) => console.log('error: ', error));

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

การหยุดการประมวลผลหลังจากการแก้ไข / ปฏิเสธ:

นี่คือสิ่งที่โฟลว์คอนโทรล JS มาตรฐาน

  • ย้อนกลับหลังresolve/ reject:

  • Return with the resolve/ reject- เนื่องจากค่าที่ส่งคืนของการเรียกกลับถูกเพิกเฉยเราสามารถบันทึกบรรทัดได้โดยส่งคืนคำสั่งปฏิเสธ / แก้ไข:

  • ใช้บล็อก if / else:

ฉันชอบที่จะใช้หนึ่งในreturnตัวเลือกที่เป็นรหัสประจบ


28
น่าสังเกตว่ารหัสจะไม่ทำงานแตกต่างกันหากreturnมีหรือไม่เพราะเมื่อตั้งค่าสถานะสัญญาแล้วจะไม่สามารถเปลี่ยนแปลงได้ดังนั้นการโทรresolve()หลังจากการโทรreject()จะไม่ทำอะไรเลยยกเว้นใช้รอบ CPU เพิ่มเติมสองสามรอบ ฉันเองจะใช้returnเพียงแค่ความสะอาดของรหัสและประสิทธิภาพในการดู แต่ไม่จำเป็นในตัวอย่างเฉพาะนี้
jfriend00

1
ลองใช้Promise.try(() => { })แทนสัญญาใหม่และหลีกเลี่ยงการใช้การแก้ไข / ปฏิเสธการโทร แต่คุณสามารถเขียนreturn denominator === 0 ? throw 'Cannot divide by zero' : numerator / denominator; ฉันใช้Promise.tryเป็นเครื่องมือในการเตะสัญญาและกำจัดสัญญาที่ห่อไว้ในบล็อกลอง / จับซึ่งเป็นปัญหา
kingdango

2
เป็นเรื่องดีที่รู้และฉันชอบรูปแบบ อย่างไรก็ตามในเวลานี้Promise.tryเป็นข้อเสนอแนะระดับ 0 ดังนั้นคุณสามารถใช้กับshimหรือใช้ไลบรารีสัญญาเช่น bluebird หรือ Q.
Ori Drori

6
@ jfriend00 เห็นได้ชัดในตัวอย่างง่ายๆนี้รหัสจะไม่ทำงานแตกต่างกัน แต่ถ้าคุณมีรหัสหลังจากrejectสิ่งที่มีราคาแพงเช่นการเชื่อมต่อกับฐานข้อมูลหรือจุดสิ้นสุด API ทุกอย่างจะไม่จำเป็นและคุณต้องเสียเงินและทรัพยากรโดยเฉพาะอย่างยิ่งหากคุณกำลังเชื่อมต่อกับบางสิ่งเช่นฐานข้อมูล AWS หรือ API Gateway endpoint ในกรณีนั้นคุณจะต้องใช้การส่งคืนเพื่อหลีกเลี่ยงการเรียกใช้รหัสที่ไม่จำเป็น
Jake Wilson

3
@ JakeWilson - แน่นอนว่าเป็นเพียงโค้ดโฟลว์ปกติใน Javascript และไม่มีส่วนเกี่ยวข้องกับสัญญาเลย หากคุณกำลังทำการประมวลผลการทำงานและไม่ต้องการรหัสใด ๆ returnเพิ่มเติมในการดำเนินการในเส้นทางรหัสปัจจุบันคุณแทรก
jfriend00

37

สำนวนทั่วไปซึ่งอาจจะใช่หรือไม่ใช่ถ้วยชาของคุณคือการรวมreturnกับreject, เพื่อปฏิเสธสัญญาและออกจากฟังก์ชั่นพร้อมกันเพื่อให้ส่วนที่เหลือของฟังก์ชั่นรวมถึงresolveไม่ได้ดำเนินการ ถ้าคุณชอบสไตล์นี้มันสามารถทำให้โค้ดของคุณมีขนาดเล็กลงได้อีก

function divide(numerator, denominator) {
  return new Promise((resolve, reject) => {
    if (denominator === 0) return reject("Cannot divide by 0");
                           ^^^^^^^^^^^^^^
    resolve(numerator / denominator);
  });
}

ใช้งานได้ดีเนื่องจากตัวสร้างสัญญาจะไม่ทำสิ่งใดกับค่าส่งคืนและในกรณีใด ๆresolveและrejectไม่ส่งคืนสิ่งใด

สำนวนเดียวกันสามารถใช้กับสไตล์การโทรกลับที่แสดงในคำตอบอื่น:

function divide(nom, denom, cb){
  if(denom === 0) return cb(Error("Cannot divide by zero"));
                  ^^^^^^^^^
  cb(null, nom / denom);
} 

อีกครั้งวิธีนี้ใช้ได้ผลเพราะผู้ที่โทรมาdivideไม่คาดหวังว่าจะส่งคืนสิ่งใดและไม่ได้ทำอะไรกับค่าส่งคืน


6
ฉันไม่ชอบสิ่งนี้. สิ่งนี้ทำให้ความคิดที่ว่าคุณคืนสิ่งที่คุณไม่ได้เป็นจริง คุณกำลังเรียกใช้ฟังก์ชั่นการปฏิเสธและจากนั้นใช้การส่งคืนเพื่อสิ้นสุดการดำเนินการฟังก์ชั่น เก็บไว้ในสายแยกสิ่งที่คุณกำลังทำจะสับสนคนเท่านั้น การอ่านรหัสเป็นสิ่งที่สำคัญ
K - ความเป็นพิษใน SO กำลังเพิ่มขึ้น

7
@KarlMorrison ในความเป็นจริงคุณกลับมา "บางสิ่ง" สัญญาปฏิเสธ ฉันคิดว่า "ความคิด" ที่คุณพูดถึงเป็นเรื่องส่วนตัวมาก ไม่มีอะไรผิดปกติกับการคืนrejectสถานะ
Frondor

5
@ ฟรอนดอร์ฉันไม่คิดว่าคุณเข้าใจสิ่งที่ฉันเขียน แน่นอนคุณและฉันเข้าใจสิ่งนี้ไม่มีอะไรเกิดขึ้นเมื่อคืนการปฏิเสธในบรรทัดเดียวกัน แต่สิ่งที่เกี่ยวกับนักพัฒนาที่ไม่คุ้นเคยกับ JavaScript ที่เข้ามาในโครงการ? การเขียนโปรแกรมประเภทนี้จะลดความสามารถในการอ่านสำหรับบุคคลดังกล่าว ระบบนิเวศของจาวาสคริปต์ในวันนี้มีความยุ่งเหยิงเพียงพอและผู้คนที่เผยแพร่การฝึกฝนประเภทนี้จะยิ่งทำให้แย่ลง นี่คือการปฏิบัติที่ไม่ดี
K - ความเป็นพิษใน SO กำลังเพิ่มขึ้น

1
@KarlMorrison ความคิดเห็นส่วนตัว! = การปฏิบัติที่ไม่ดี มันอาจจะช่วยให้นักพัฒนา Javascript ใหม่เข้าใจว่าเกิดอะไรขึ้นกับการคืนสินค้า
Toby

1
@TobyCaulk หากผู้คนต้องการเรียนรู้ว่าผลตอบแทนคืออะไรพวกเขาไม่ควรเล่นกับสัญญาด้วยพวกเขาควรจะเรียนรู้การเขียนโปรแกรมพื้นฐาน
K - ความเป็นพิษใน SO กำลังเพิ่มขึ้น

10

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

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

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


1นี้ในทางเทคนิคให้คำตอบขึ้นอยู่กับความจริงที่ว่าในกรณีนี้รหัสหลังจากที่ "ผลตอบแทน" มันควรจะถูกมองข้ามจะไม่ส่งผลให้เกิดผลข้างเคียง JavaScript จะแบ่งเป็นศูนย์อย่างมีความสุขและคืนค่า + Infinity / -Infinity หรือ NaN


เชิงอรรถที่ดี !!
HankCa

9

หากคุณไม่ "กลับมา" หลังจากแก้ไข / ปฏิเสธสิ่งที่ไม่ดี (เช่นการเปลี่ยนเส้นทางหน้า) อาจเกิดขึ้นได้หลังจากที่คุณตั้งใจให้มันหยุด ที่มา: ฉันวิ่งเข้าไปในนี้


6
+1 สำหรับตัวอย่าง ฉันมีปัญหาที่โปรแกรมของฉันจะสร้างคิวรีฐานข้อมูลที่ไม่ถูกต้อง 100+ และฉันไม่สามารถหาสาเหตุได้ ปรากฎว่าฉันไม่ได้ "กลับมา" หลังจากถูกปฏิเสธ มันผิดพลาดเล็กน้อย แต่ฉันเรียนรู้บทเรียนของฉัน
AdamInTheOculus

8

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

โปรดทราบว่าการรับreturnช่วงต้นเป็นเรื่องธรรมดามากในการโทรกลับ:

function divide(nom, denom, cb){
     if(denom === 0){
         cb(Error("Cannot divide by zero");
         return; // unlike with promises, missing the return here is a mistake
     }
     cb(null, nom / denom); // this will divide by zero. Since it's a callback.
} 

ดังนั้นในขณะที่มันเป็นแนวปฏิบัติที่ดีในสัญญามันเป็นสิ่งจำเป็นกับการเรียกกลับ หมายเหตุบางประการเกี่ยวกับรหัสของคุณ:

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

4

ในหลายกรณีก็เป็นไปได้ในการตรวจสอบพารามิเตอร์แยกต่างหากและทันทีที่กลับมาเป็นสัญญาปฏิเสธกับPromise.reject (เหตุผล)

function divide2(numerator, denominator) {
  if (denominator === 0) {
    return Promise.reject("Cannot divide by 0");
  }
  
  return new Promise((resolve, reject) => {
    resolve(numerator / denominator);
  });
}


divide2(4, 0).then((result) => console.log(result), (error) => console.log(error));

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