การปฏิเสธสัญญาสำหรับกรณีข้อผิดพลาดเท่านั้นหรือไม่


25

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


หากการตรวจสอบล้มเหลวคุณควรrejectและไม่ได้กลับเท็จ แต่ถ้าคุณจะคาดหวังว่าคุ้มค่าที่จะเป็นBoolแล้วคุณก็ประสบความสำเร็จและคุณควรจะแก้ไขด้วย Bool โดยไม่คำนึงถึงความคุ้มค่า สัญญาที่มีการจัดเรียงของผู้รับมอบฉันทะค่า - rejectพวกเขาเก็บค่าที่ส่งกลับเท่านั้นดังนั้นถ้าค่าไม่สามารถได้คุณควร resolveมิฉะนั้นคุณควร

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

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

4
อีกวิธีหนึ่งที่จะคิดเกี่ยวกับมัน - ถ้าครั้งนี้มีวิธีการเรียกซิงโครคุณจะรักษาความล้มเหลวในการตรวจสอบตามปกติ (ชื่อผู้ใช้ที่ไม่ดี / รหัสผ่าน) ที่กลับมาfalseหรือการขว้างปายกเว้น?
wrschneider

2
Fetch APIเป็นตัวอย่างที่ดีในเรื่องนี้ มันก็จะก่อให้เกิดการthenเมื่อตอบสนองเซิร์ฟเวอร์ - แม้ว่ารหัสข้อผิดพลาดจะถูกส่งกลับ - response.okและคุณต้องตรวจสอบ catchจัดการจะถูกเรียกเฉพาะที่ไม่คาดคิดเกิดข้อผิดพลาด
CodingIntrigue

คำตอบ:


22

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

การปฏิเสธ a Promiseจะเหมือนกับการยกข้อยกเว้น ไม่ได้ผลลัพธ์ที่ไม่พึงประสงค์เป็นพิเศษ , ผลมาจากข้อผิดพลาด คุณสามารถโต้แย้งกรณีของคุณทั้งสองวิธี:

  1. รับรองความถูกต้องล้มเหลวควรเพราะโทรคาดว่าวัตถุในการตอบแทนและสิ่งอื่นที่เป็นข้อยกเว้นที่จะไหลนี้rejectPromiseUser

  2. รับรองความถูกต้องล้มเหลวควรแม้ว่าจะตั้งแต่การให้ข้อมูลประจำตัวที่ไม่ถูกต้องไม่ได้จริงๆพิเศษกรณีและโทรไม่ควรคาดหวังว่าการไหลที่มักจะส่งผลในการresolvePromisenullUser

โปรดทราบว่าฉันกำลังมองหาที่ปัญหาจากด้านข้างของผู้โทร ในการไหลของข้อมูลผู้โทรคาดหวังว่าการกระทำของเขาจะส่งผลให้เกิดUser(และสิ่งอื่นใดเป็นข้อผิดพลาด) หรือมันเหมาะสมสำหรับผู้โทรรายนี้ที่จะจัดการกับผลลัพธ์อื่น ๆ หรือไม่?

ในระบบหลายชั้นคำตอบอาจเปลี่ยนไปเมื่อข้อมูลไหลผ่านเลเยอร์ ตัวอย่างเช่น:

  • เลเยอร์ HTTP บอกว่าแก้ปัญหา! การร้องขอถูกส่งซ็อกเก็ตปิดอย่างสะอาดและเซิร์ฟเวอร์ส่งการตอบสนองที่ถูกต้อง Fetch APIไม่นี้
  • ชั้นโปรโตคอลแล้วพูดว่าปฏิเสธ! รหัสสถานะในการตอบสนองคือ 401 ซึ่งก็โอเคสำหรับ HTTP แต่ไม่ใช่สำหรับโปรโตคอล!
  • เลเยอร์การรับรองความถูกต้องบอกว่าไม่แก้ปัญหา มันจับข้อผิดพลาดเนื่องจาก 401 เป็นสถานะที่คาดไว้สำหรับรหัสผ่านที่ไม่ถูกต้องและแก้ไขให้กับnullผู้ใช้
  • ตัวควบคุมส่วนต่อประสานบอกว่าไม่มีสิ่งนั้นเลยปฏิเสธ! คำกริยาที่แสดงบนหน้าจอคาดว่าจะมีชื่อผู้ใช้และรูปประจำตัวและสิ่งอื่นที่ไม่ใช่ข้อมูลนั้นเป็นข้อผิดพลาด

ตัวอย่าง 4 จุดนี้ซับซ้อนอย่างเห็นได้ชัด แต่แสดง 2 จุด

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

ดังนั้นอีกครั้งไม่มีคำตอบที่ยาก ถึงเวลาคิดและออกแบบ!


6

ดังนั้นสัญญามีคุณสมบัติที่ดีที่พวกเขานำ JS จากภาษาที่ใช้งานได้ซึ่งก็คือพวกเขาใช้Eitherตัวสร้างประเภทนี้ที่ติดกาวสองประเภทอื่น ๆ ประเภทLeftและRightประเภทโดยการบังคับตรรกะให้ใช้สาขาหนึ่งหรือสาขาอื่น สาขา.

data Either x y = Left x | Right y

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

เหตุผลก็คือ JS จะรับใบthrowแจ้งยอดจากสัญญาการจัดการรหัสและรวมไว้ในLeftด้านข้างของมันเช่นกัน เทคนิคใน JS คุณสามารถthrowทุกอย่างรวมถึงเท็จจริง / หรือสตริงหรือตัวเลข แต่โค้ด JavaScript ยังพ่นสิ่งที่ไม่throw (เมื่อคุณทำสิ่งต่างๆเช่นการพยายามที่จะเข้าถึงคุณสมบัติใน nulls) และมี API ตกลงนี้ ( Errorวัตถุ) . ดังนั้นเมื่อคุณได้รับการจับมันมักจะดีที่จะสามารถสันนิษฐานได้ว่าข้อผิดพลาดเหล่านั้นเป็นErrorวัตถุ และเนื่องจากrejectคำสัญญาที่จะรวมตัวกันในข้อผิดพลาดใด ๆ จากข้อผิดพลาดใด ๆ ข้างต้นโดยทั่วไปคุณต้องการเฉพาะthrowข้อผิดพลาดอื่น ๆ เพื่อให้catchคำสั่งของคุณมีตรรกะที่เรียบง่ายและสอดคล้องกัน

ดังนั้นแม้ว่าคุณจะสามารถใส่ if-conditional ไว้ในของคุณcatchและมองหาข้อผิดพลาดที่ผิดพลาดซึ่งในกรณีนี้ความจริงเป็นเรื่องเล็กน้อย

Either (Either Error ()) ()

คุณอาจจะต้องการโครงสร้างตรรกะอย่างน้อยสำหรับสิ่งที่เกิดขึ้นทันทีที่ออกมาจากตัวตนของบูลีนที่เรียบง่าย:

Either Error Bool

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

Either Error (Maybe User)

และนี่คือมากกว่าหรือน้อยกว่าสิ่งที่ผมคาดหวังผลตอบแทนในกรณีที่ผู้ใช้ไม่ได้กำหนดไว้เป็นอย่างอื่นกลับnull {user_id: <number>, permission_to_launch_missiles: <boolean>}ฉันคาดหวังว่ากรณีทั่วไปไม่ได้ถูกบันทึกไว้ในคือรอดตัวอย่างเช่นถ้าเราอยู่ในการจัดเรียงของ "สาธิตให้กับลูกค้าใหม่โหมด" บางส่วนและไม่ควรผสมกับข้อบกพร่องที่ฉันตั้งใจเรียกว่าobject.doStuff()เมื่อเป็นobject.doStuffundefined

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

function launchMissiles() {
    function actuallyLaunchThem() {
        // stub
    }
    return getAuth().then(auth => {
        if (auth === null) {
            throw new PermissionError('Cannot launch missiles without permission, cannot have permission if not logged in.');
        } else if (auth.permission_to_launch_missiles) {
            return actuallyLaunchThem();
        } else {
            throw new PermissionError(`User ${auth.user_id} does not have permission to launch the missiles.`);
        }
    });
}

3

ข้อผิดพลาด

พูดคุยเกี่ยวกับข้อผิดพลาด

ข้อผิดพลาดมีสองประเภท:

  • ข้อผิดพลาดที่คาดไว้
  • ข้อผิดพลาดที่ไม่คาดคิด
  • ข้อผิดพลาดแบบ off-one

ข้อผิดพลาดที่คาดหวัง

ข้อผิดพลาดที่คาดหวังคือสถานะที่สิ่งผิดปกติเกิดขึ้น แต่คุณรู้ว่ามันอาจเกิดขึ้นดังนั้นคุณต้องจัดการกับมัน

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

สิ่งเหล่านี้สามารถกู้คืนได้เมื่อจัดการ หากปล่อยทิ้งไว้จะกลายเป็นข้อผิดพลาดที่ไม่คาดคิด

ข้อผิดพลาดที่ไม่คาดคิด

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

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

try..catch

มาพูดถึงtry..catchกัน

ใน JavaScript throwไม่ได้ใช้กันทั่วไป หากคุณมองไปรอบ ๆ ตัวอย่างในโค้ดพวกมันจะอยู่ห่างกันไม่มากนักและมักจะมีโครงสร้างตามแนวของ

function example(param) {
  if (!Array.isArray(param) {
    throw new TypeError('"param" should be an array!');
  }
  ...
}

ด้วยเหตุนี้try..catchบล็อกจึงไม่ใช่สิ่งที่พบได้บ่อยสำหรับการควบคุมโฟลว์ โดยปกติแล้วการเพิ่มการตรวจสอบก่อนการเรียกใช้จะทำได้ง่ายมาก

สภาพแวดล้อมของ JavaScript ก็ค่อนข้างให้อภัยเช่นกันดังนั้นข้อผิดพลาดที่ไม่คาดคิดก็มักจะไม่ถูกตรวจสอบเช่นกัน

try..catchไม่จำเป็นต้องเป็นเรื่องผิดปกติ มีบางกรณีการใช้งานที่ดีซึ่งพบได้ทั่วไปในภาษาเช่น Java และ C # Java และ C # มีข้อดีของการcatchสร้างแบบพิมพ์เพื่อให้คุณสามารถแยกความแตกต่างระหว่างข้อผิดพลาดที่คาดหวังและข้อผิดพลาดที่ไม่คาดคิด:

C # :
try
{
  var example = DoSomething();
}
catch (ExpectedException e)
{
  DoSomethingElse(e);
}

ตัวอย่างนี้อนุญาตให้มีข้อยกเว้นอื่น ๆ ที่ไม่คาดคิดไหลขึ้นและได้รับการจัดการที่อื่น (เช่นโดยการบันทึกและปิดโปรแกรม)

ใน JavaScript โครงสร้างนี้สามารถจำลองได้ผ่าน:

try {
  let example = doSomething();
} catch (e) {
  if (e instanceOf ExpectedError) {
    DoSomethingElse(e);
  } else {
    throw e;
  }
}

ไม่สง่างามซึ่งเป็นส่วนหนึ่งของเหตุผลว่าทำไมมันถึงผิดปกติ

ฟังก์ชั่น

พูดคุยเกี่ยวกับฟังก์ชั่น

หากคุณใช้หลักการความรับผิดชอบเดียวแต่ละชั้นเรียนและฟังก์ชั่นควรตอบสนองวัตถุประสงค์ที่เป็นเอกเทศ

ตัวอย่างเช่นauthenticate()อาจรับรองความถูกต้องของผู้ใช้

สิ่งนี้อาจเขียนเป็น:

const user = authenticate();
if (user == null) {
  // keep doing stuff
} else {
  // handle expected error
}

หรืออาจเขียนเป็น:

try {
  const user = authenticate();
  // keep doing stuff
} catch (e) {
  if (e instanceOf AuthenticationError) {
    // handle expected error
  } else {
    throw e;
  }
}

ทั้งสองเป็นที่ยอมรับได้

สัญญา

พูดคุยเกี่ยวกับสัญญา

try..catchสัญญามีรูปแบบที่ไม่ตรงกันของ โทรnew PromiseหรือPromise.resolveเริ่มtryรหัสของคุณ โทรthrowหรือPromise.rejectส่งcatchรหัสไปให้คุณ

Promise.resolve(value)   // try
  .then(doSomething)     // try
  .then(doSomethingElse) // try
  .catch(handleError)    // catch

หากคุณมีฟังก์ชั่นแบบอะซิงโครนัสเพื่อตรวจสอบสิทธิ์ผู้ใช้คุณอาจเขียนเป็น:

authenticate()
  .then((user) => {
    if (user == null) {
      // keep doing stuff
    } else {
      // handle expected error
    }
  });

หรืออาจเขียนเป็น:

authenticate()
  .then((user) => {
    // keep doing stuff
  })
  .catch((e) => {
    if (e instanceOf AuthenticationError) {
      // handle expected error
    } else {
      throw e;
    }
  });

ทั้งสองเป็นที่ยอมรับได้

การทำรัง

พูดคุยเกี่ยวกับการทำรัง

try..catchสามารถซ้อนกันได้ authenticate()วิธีการของคุณอาจมีtry..catchบล็อกภายในเช่น:

try {
  const credentials = requestCredentialsFromUser();
  const user = getUserFromServer(credentials);
} catch (e) {
  if (e instanceOf CredentialsError) {
    // handle failure to request credentials
  } else if (e instanceOf ServerError) {
    // handle failure to get data from server
  } else {
    throw e; // no idea what happened
  }
}

คำสัญญาในทำนองเดียวกันสามารถซ้อนกันได้ authenticate()วิธีการasync ของคุณอาจใช้สัญญา:

requestCredentialsFromUser()
  .then(getUserFromServer)
  .catch((e) => {
    if (e instanceOf CredentialsError) {
      // handle failure to request credentials
    } else if (e instanceOf ServerError) {
      // handle failure to get data from server
    } else {
      throw e; // no idea what happened
    }
  });

ดังนั้นคำตอบคืออะไร

ตกลงฉันคิดว่าถึงเวลาที่ฉันต้องตอบคำถามจริง:

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

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

หากขั้นตอนการควบคุมของคุณง่ายขึ้นโดยมีการifตรวจสอบในthenงบของคุณไม่จำเป็นต้องปฏิเสธสัญญา

หากโฟลว์การควบคุมของคุณง่ายขึ้นโดยการปฏิเสธสัญญาและจากนั้นตรวจสอบชนิดของข้อผิดพลาดในรหัสการจัดการข้อผิดพลาดให้ทำเช่นนั้น


0

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


ครูสอนศาสนาส่วนใหญ่ฉันรู้ว่าจะไม่เห็นด้วยกับคุณ

0

การจัดการสัญญาเป็นไปตามเงื่อนไข "ถ้า" มากกว่าหรือน้อยกว่า ขึ้นอยู่กับคุณว่าคุณต้องการ "แก้ไข" หรือ "ปฏิเสธ" หากการตรวจสอบสิทธิ์ล้มเหลว


1
สัญญาเป็นตรงกันไม่try..catch if
zzzzBov

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

@ บางสิ่งที่ไม่คุณเข้าใจผิดอาร์กิวเมนต์ของฉัน try { if (!doSomething()) throw whatever; doSomethingElse() } catch { ... }สมบูรณ์ดี แต่โครงสร้างที่Promiseหมายถึงคือtry..catchส่วนไม่ใช่ifส่วน
zzzzBov

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

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