สัญญาจาวาสคริปต์ - ปฏิเสธกับการโยน


384

ฉันได้อ่านบทความหลายเรื่องในหัวข้อนี้ แต่ก็ยังไม่ชัดเจนสำหรับฉันหากมีข้อแตกต่างระหว่างPromise.rejectกับการส่งข้อผิดพลาด ตัวอย่างเช่น,

ใช้ Promise.reject

return asyncIsPermitted()
    .then(function(result) {
        if (result === true) {
            return true;
        }
        else {
            return Promise.reject(new PermissionDenied());
        }
    });

โดยใช้การโยน

return asyncIsPermitted()
    .then(function(result) {
        if (result === true) {
            return true;
        }
        else {
            throw new PermissionDenied();
        }
    });

การตั้งค่าของฉันคือการใช้throwเพียงเพราะมันสั้นกว่า แต่ก็สงสัยว่ามีข้อได้เปรียบของอีกคนหนึ่ง


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

@webduvet ไม่ได้มีสัญญา - พวกเขาถูกออกแบบมาเพื่อทำงานกับการโยน
joews

15
ข้อเสียอย่างหนึ่งthrowคือมันจะไม่ส่งผลให้เกิดสัญญาที่ถูกปฏิเสธถ้ามันถูกส่งออกมาจากภายในการโทรกลับแบบอะซิงโครนัสเช่น setTimeout jsfiddle.net/m07van33 @ Blondie คำตอบของคุณถูกต้อง
Kevin B

@ Jojo มันไม่ได้หมายความว่ามันดี;)
webduvet

1
อาจริง ดังนั้นการชี้แจงความคิดเห็นของฉันจะเป็น"ถ้ามันถูกโยนจากภายในการเรียกกลับไม่ตรงกันที่ไม่ได้ promisified " ฉันรู้ว่ามีข้อยกเว้นฉันไม่สามารถจำได้ว่ามันคืออะไร ฉันก็ชอบที่จะใช้การโยนเพียงเพราะฉันคิดว่ามันอ่านง่ายขึ้นและอนุญาตให้ฉันละเว้นrejectมันจากรายการพารามิเตอร์ของฉัน
Kevin B

คำตอบ:


344

ไม่มีประโยชน์จากการใช้ vs เทียบกับอีกอัน แต่มีบางกรณีที่throwไม่สามารถใช้งานได้ อย่างไรก็ตามกรณีเหล่านี้สามารถแก้ไขได้

throwเมื่อใดก็ตามที่คุณอยู่ในการเรียกกลับสัญญาคุณสามารถใช้ แต่ถ้าคุณอยู่ในการเรียกกลับไม่ตรงกันอื่น ๆ rejectที่คุณต้องใช้

ตัวอย่างเช่นสิ่งนี้จะไม่ทริกเกอร์การจับ:

new Promise(function() {
  setTimeout(function() {
    throw 'or nah';
    // return Promise.reject('or nah'); also won't work
  }, 1000);
}).catch(function(e) {
  console.log(e); // doesn't happen
});

แต่คุณจะเหลือสัญญาที่ไม่ได้รับการแก้ไขและข้อยกเว้นที่ไม่ได้ตรวจสอบ rejectนั่นคือกรณีที่คุณต้องการจะใช้งานแทน อย่างไรก็ตามคุณสามารถแก้ไขได้สองวิธี

  1. โดยใช้ฟังก์ชั่นการปฏิเสธสัญญาเดิมภายในระยะเวลา:

new Promise(function(resolve, reject) {
  setTimeout(function() {
    reject('or nah');
  }, 1000);
}).catch(function(e) {
  console.log(e); // works!
});

  1. โดยการแจ้งการหมดเวลา:

function timeout(duration) { // Thanks joews
  return new Promise(function(resolve) {
    setTimeout(resolve, duration);
  });
}

timeout(1000).then(function() {
  throw 'worky!';
  // return Promise.reject('worky'); also works
}).catch(function(e) {
  console.log(e); // 'worky!'
});


54
เป็นมูลค่าการกล่าวขวัญว่าสถานที่ในการโทรกลับ async ที่ไม่ได้รับการแนะนำที่คุณไม่สามารถใช้ได้คุณไม่throw errorสามารถใช้งานได้return Promise.reject(err)ซึ่งเป็นสิ่งที่ OP ขอให้เราเปรียบเทียบ นี่คือเหตุผลที่คุณไม่ควรใส่การเรียกกลับแบบ async ไว้ในสัญญา แนะนำทุกอย่างที่เป็น async แล้วคุณไม่มีข้อ จำกัด เหล่านี้
jfriend00

9
"อย่างไรก็ตามหากคุณอยู่ในการติดต่อกลับประเภทอื่น ๆ " ควรเป็น "อย่างไรก็ตามหากคุณอยู่ในประเภทติดต่อกลับแบบอื่น ๆ" การโทรกลับสามารถซิงโครนัส (เช่นกับArray#forEach) และกับสิ่งเหล่านั้น
Félix Saparelli

2
@KevinB กำลังอ่านบรรทัดเหล่านี้ "มีบางกรณีที่การโยนไม่สามารถทำได้" และ "ทุกครั้งที่คุณอยู่ในการติดต่อกลับสัญญาคุณสามารถใช้การโยนได้อย่างไรก็ตามหากคุณอยู่ในการติดต่อกลับแบบอะซิงโครนัสอื่น ๆ คุณต้องใช้การปฏิเสธ" ฉันรู้สึกว่าตัวอย่างตัวอย่างจะแสดงกรณีที่throwไม่สามารถใช้งานได้และPromise.rejectเป็นตัวเลือกที่ดีกว่า อย่างไรก็ตามตัวอย่างจะไม่ได้รับผลกระทบใด ๆ กับสองตัวเลือกและให้ผลลัพธ์เดียวกันโดยไม่คำนึงถึงสิ่งที่คุณเลือก ฉันพลาดอะไรไปรึเปล่า?
Anshul

2
ใช่. หากคุณใช้การโยนใน setTimeout การดักจับจะไม่ถูกเรียก คุณต้องใช้สิ่งrejectที่ถูกส่งผ่านไปยังการnew Promise(fn)ติดต่อกลับ
Kevin B

2
@KevinB ขอบคุณสำหรับการเข้าพัก ตัวอย่างที่ได้รับจาก OP กล่าวถึงเขาโดยเฉพาะอยากจะเปรียบเทียบและreturn Promise.reject() throwเขาไม่ได้พูดถึงการrejectโทรกลับที่ให้ไว้ในการnew Promise(function(resolve, reject))สร้าง ดังนั้นในขณะที่ตัวอย่างข้อมูลทั้งสองของคุณสาธิตอย่างถูกต้องเมื่อคุณควรใช้การแก้ไขปัญหาการโทรกลับคำถามของ OP ไม่ได้เป็นเช่นนั้น
Anshul

200

ข้อเท็จจริงที่สำคัญอีกข้อหนึ่งคือreject() ไม่ได้ยกเลิกโฟลว์การควบคุมเหมือนreturnคำสั่ง ในทางตรงกันข้ามthrowจะยุติการควบคุมการไหล

ตัวอย่าง:

new Promise((resolve, reject) => {
  throw "err";
  console.log("NEVER REACHED");
})
.then(() => console.log("RESOLVED"))
.catch(() => console.log("REJECTED"));

VS

new Promise((resolve, reject) => {
  reject(); // resolve() behaves similarly
  console.log("ALWAYS REACHED"); // "REJECTED" will print AFTER this
})
.then(() => console.log("RESOLVED"))
.catch(() => console.log("REJECTED"));


50
ประเด็นก็ถูกต้อง แต่การเปรียบเทียบนั้นยุ่งยาก เพราะโดยปกติคุณควรส่งคืนสัญญาที่ถูกปฏิเสธโดยการเขียนreturn reject()ดังนั้นบรรทัดถัดไปจะไม่ทำงาน
อาริโซน่า

7
ทำไมคุณต้องการส่งคืน
lukyer

31
ในกรณีนี้return reject()เป็นเพียงการจดชวเลขสำหรับreject(); returnสิ่งที่คุณต้องการคือยุติการไหล ค่าตอบแทนของผู้บริหาร (ฟังก์ชั่นที่ผ่านมาnew Promise) ไม่ได้ใช้เพื่อให้มีความปลอดภัย
Félix Saparelli

47

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

ตัวอย่างที่ฉันเคยเห็นที่ช่วยอธิบายปัญหาให้ฉันก็คือคุณสามารถตั้งค่าฟังก์ชันไทม์เอาต์ด้วยการปฏิเสธตัวอย่างเช่น

new Promise(_, reject) {
 setTimeout(reject, 3000);
});

ข้างต้นอาจเป็นไปไม่ได้ที่จะเขียนด้วยการโยน

ในตัวอย่างเล็ก ๆ ของคุณความแตกต่างในการแยกไม่ออก แต่เมื่อจัดการกับแนวคิดอะซิงโครนัสที่ซับซ้อนกว่าความแตกต่างระหว่างทั้งสองอาจรุนแรงมาก


1
ฟังดูเหมือนแนวคิดสำคัญ แต่ฉันไม่เข้าใจเหมือนที่เขียนไว้ ยังใหม่กับสัญญาฉันยังเดาเกินไป
David Spector

43

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

ตัวอย่างเฉพาะของคุณทำให้เสียความแตกต่างที่สำคัญระหว่างพวกเขา:

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

พิจารณาสถานการณ์ด้านล่าง:

checkCredentials = () => {
    let idToken = localStorage.getItem('some token');
    if ( idToken ) {
      return fetch(`https://someValidateEndpoint`, {
        headers: {
          Authorization: `Bearer ${idToken}`
        }
      })
    } else {
      throw new Error('No Token Found In Local Storage')
    }
  }

นี่จะเป็นรูปแบบการต่อต้านเพราะคุณจะต้องรองรับทั้งกรณี async และ sync อาจมีลักษณะเช่น:

try {
  function onFulfilled() { ... do the rest of your logic }
  function onRejected() { // handle async failure - like network timeout }
  checkCredentials(x).then(onFulfilled, onRejected);
} catch (e) {
  // Error('No Token Found In Local Storage')
  // handle synchronous failure
} 

ไม่ดีและนี่คือสิ่งที่Promise.reject(ที่มีอยู่ในขอบเขตทั่วโลก) throwมาช่วยและมีประสิทธิภาพแตกต่างจาก Refactor ตอนนี้กลายเป็น:

checkCredentials = () => {
  let idToken = localStorage.getItem('some_token');
  if (!idToken) {
    return Promise.reject('No Token Found In Local Storage')
  }
  return fetch(`https://someValidateEndpoint`, {
    headers: {
      Authorization: `Bearer ${idToken}`
    }
  })
}

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

checkCredentials()
      .catch((error) => if ( error == 'No Token' ) {
      // do no token modal
      } else if ( error === 400 ) {
      // do not authorized modal. etc.
      }

1
อย่างไรก็ตามตัวอย่างของ Op จะคืนสัญญาเสมอ คำถามหมายถึงว่าคุณควรใช้Promise.rejectหรือthrowเมื่อคุณต้องการส่งคืนสัญญาที่ถูกปฏิเสธ (สัญญาที่จะข้ามไปยังสัญญาถัดไป.catch())
Marcos Pereira

@maxwell - ฉันชอบคุณตัวอย่าง ในเวลาเดียวกันถ้าในการเรียกคุณจะเพิ่มการจับและในนั้นคุณโยนข้อยกเว้นแล้วคุณจะปลอดภัยที่จะใช้ลอง ... catch ... ไม่มีโลกที่สมบูรณ์แบบในการไหลยกเว้น แต่ฉันคิดว่าการใช้อย่างใดอย่างหนึ่ง รูปแบบเดียวทำให้รู้สึกและการรวมรูปแบบไม่ปลอดภัย (สอดคล้องกับรูปแบบของคุณเทียบกับการเปรียบเทียบรูปแบบต่อต้าน)
user3053247

1
คำตอบที่ยอดเยี่ยม แต่ฉันพบว่านี่เป็นข้อบกพร่อง - รูปแบบนี้ถือว่าข้อผิดพลาดทั้งหมดได้รับการจัดการโดยส่งคืน Promise.reject - จะเกิดอะไรขึ้นกับข้อผิดพลาดที่ไม่คาดคิดทั้งหมดที่อาจถูกโยนจาก checkCredentials ()
chenop

1
ใช่คุณพูดถูก @chenop - เพื่อตรวจจับข้อผิดพลาดที่ไม่คาดคิดคุณจะต้องลองดู / จับให้ได้
maxwell

ฉันไม่เข้าใจกรณีของ @ maxwell คุณไม่สามารถสร้างมันขึ้นมาเพื่อทำเช่นcheckCredentials(x).then(onFulfilled).catch(e) {}นั้นและมีcatchมือจับทั้งเคสปฏิเสธและเคสโยนผิดพลาด?
Ben Wheeler

5

ตัวอย่างที่จะลอง เพียงแค่เปลี่ยนเป็นรุ่นโยนเป็นเท็จเพื่อใช้ปฏิเสธแทนที่จะโยน

const isVersionThrow = true

class TestClass {
  async testFunction () {
    if (isVersionThrow) {
      console.log('Throw version')
      throw new Error('Fail!')
    } else {
      console.log('Reject version')
      return new Promise((resolve, reject) => {
        reject(new Error('Fail!'))
      })
    }
  }
}

const test = async () => {
  const test = new TestClass()
  try {
    var response = await test.testFunction()
    return response 
  } catch (error) {
    console.log('ERROR RETURNED')
    throw error 
  }  
}

test()
.then(result => {
  console.log('result: ' + result)
})
.catch(error => {
  console.log('error: ' + error)
})

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