จะปฏิเสธในรูปแบบ async / รอคอยไวยากรณ์ได้อย่างไร


282

ฉันจะปฏิเสธคำสัญญาที่ส่งคืนโดยฟังก์ชัน async / await ได้อย่างไร

เช่นเดิม

foo(id: string): Promise<A> {
  return new Promise((resolve, reject) => {
    someAsyncPromise().then((value)=>resolve(200)).catch((err)=>reject(400))
  });
}

แปลเป็น async / รอคอย

async foo(id: string): Promise<A> {
  try{
    await someAsyncPromise();
    return 200;
  } catch(error) {//here goes if someAsyncPromise() rejected}
    return 400; //this will result in a resolved promise.
  });
}

ดังนั้นฉันจะปฏิเสธสัญญานี้ในกรณีนี้ได้อย่างไร?


20
หลีกเลี่ยงการPromiseสร้าง antipattern ! แม้แต่ข้อมูลชิ้นแรกควรถูกเขียนขึ้นด้วยfoo(id: string): Promise<A> { return someAsyncPromise().then(()=>{ return 200; }, ()=>{ throw 400; }); }
Bergi

10
ฉันคิดว่ามันจะเป็นประโยชน์ในการแปลรหัสในคำถามนี้เป็น Vanilla JS เนื่องจากคำถามไม่มีส่วนเกี่ยวข้องกับ TypeScript หากฉันทำเช่นนั้นการแก้ไขนั้นน่าจะได้รับการยอมรับหรือไม่
Jacob Ford

คำตอบ:


328

ทางออกที่ดีที่สุดของคุณคือthrowการErrorห่อค่าซึ่งส่งผลให้สัญญาถูกปฏิเสธพร้อมกับErrorห่อค่า:

} catch (error) {
    throw new Error(400);
}

คุณสามารถเพียงแค่throwค่า แต่จากนั้นไม่มีข้อมูลการติดตามสแต็ก:

} catch (error) {
    throw 400;
}

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

} catch (error) {
    return Promise.reject(new Error(400));
}

(หรือเพียงreturn Promise.reject(400);แต่อีกครั้งแล้วไม่มีข้อมูลบริบท)

(ในกรณีของคุณในขณะที่คุณกำลังใช้งานTypeScriptและfooค่าการตอบโต้คือPromise<A>คุณจะใช้return Promise.reject<A>(400 /*or error*/);)

ในasync/ awaitสถานการณ์ว่าสุดท้ายอาจเป็นบิตของการจับคู่ความหมายผิดพลาด แต่ก็ใช้งานได้

หากคุณโยนสิ่งErrorที่เล่นได้ดีกับทุกสิ่งที่ใช้fooผลลัพธ์ของคุณด้วยawaitไวยากรณ์:

try {
    await foo();
} catch (error) {
    // Here, `error` would be an `Error` (with stack trace, etc.).
    // Whereas if you used `throw 400`, it would just be `400`.
}

12
และเนื่องจาก async / await นั้นเกี่ยวกับการใช้ async flow กลับไปที่ซิงก์ซิงค์throwจึงดีกว่าPromise.reject()IMO ไม่ว่าจะthrow 400เป็นคำถามที่แตกต่าง ใน OP มันปฏิเสธ 400 และเราสามารถยืนยันได้ว่ามันควรปฏิเสธ a Errorแทน
ช่วงเวลา

2
ใช่อย่างไรก็ตามถ้าเชนโค้ดของคุณใช้ async / คอยอยู่จริงๆคุณจะ ..... ยากที่จะพิมพ์ที่นี่ให้ฉันสาธิตเป็นคำตอบ
unional

1
มีเหตุผลใดบ้างที่คุณต้องการส่งข้อผิดพลาดใหม่ซึ่งตรงข้ามกับข้อผิดพลาดที่คุณได้รับในบล็อก catch
Adrian M

1
@sebastian - ฉันไม่รู้ว่าคุณหมายถึงอะไรที่นั่น ในasyncฟังก์ชั่นไม่มีresolveหรือrejectฟังก์ชั่น มีreturnและthrowซึ่งเป็นวิธีสำนวนในการแก้ไขและปฏิเสธasyncสัญญาของฟังก์ชั่น
TJ Crowder

1
@ Jan-PhilipGehrcke - คุณทำได้แต่ฉันไม่เคยทำ มันกำลังสร้างตัวอย่างnewทำให้ชัดเจน นอกจากนี้โปรดทราบว่าคุณไม่สามารถออกได้หากคุณมีErrorคลาสย่อย ( class MyError extends Error) ดังนั้น ...
TJ Crowder

146

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

await foo().catch(error => console.log(error));

วิธีนี้คุณสามารถหลีกเลี่ยงtry/catchไวยากรณ์ได้หากคุณไม่ชอบ


1
ดังนั้นถ้าผมต้องการที่จะปฏิเสธฉันasyncฟังก์ชั่นผมโยนข้อยกเว้นแล้วจับมันอย่างกับ.catch()เช่นเดียวกับถ้าผมกลับมาหรือที่เรียกว่าPromise.reject rejectฉันชอบมัน!
icl7126

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

1
@ jablesauce สำหรับกรณีการใช้งานของฉันไม่เพียง แต่ฉันจำเป็นต้องawaitแยกแต่ละความล้มเหลว แต่ฉันยังต้องทำงานกับเฟรมเวิร์กตามสัญญาซึ่งปฏิเสธสัญญาเมื่อเกิดข้อผิดพลาด
Reuven Karasik

มันไม่ได้ผลสำหรับฉัน ดูเหมือนจะไม่ถูกบล็อก catch ถ้า url ล้มเหลว [ตอบสนอง] = รอคอย oauthGet ( ${host}/user/permissions/repositories_wrong_url/, accessToken, accessTokenSecret) .catch (ผิดพลาด => {logger.error ( 'ไม่สามารถดึงข้อมูลที่เก็บสิทธิ์' ผิดพลาด); โทรกลับ (ผิดพลาด);})
sn.anurag

1
ไม่ต้องการawaitคำหลักที่นี่
Ashish Rawat

12

คุณสามารถสร้างฟังก์ชั่น wrapperที่ให้คำมั่นสัญญาและส่งกลับอาร์เรย์ด้วยข้อมูลหากไม่มีข้อผิดพลาดและข้อผิดพลาดหากมีข้อผิดพลาด

function safePromise(promise) {
  return promise.then(data => [ data ]).catch(error => [ null, error ]);
}

ใช้อย่างนี้ในES7และในฟังก์ชั่นasync :

async function checkItem() {
  const [ item, error ] = await safePromise(getItem(id));
  if (error) { return null; } // handle error and return
  return item; // no error so safe to use item
}

1
ดูเหมือนความพยายามที่จะมีไวยากรณ์ Go ที่น่ารัก แต่ไม่มีความสง่างามมากนัก ฉันพบรหัสที่ใช้เพื่อทำให้งงงวยเพียงพอที่จะดูดค่าออกจากการแก้ปัญหา
Kim

8

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

async foo(id: string): Promise<A> {
    return new Promise(function(resolve, reject) {
        // execute some code here
        if (success) { // let's say this is a boolean value from line above
            return resolve(success);
        } else {
            return reject(error); // this can be anything, preferably an Error object to catch the stacktrace from this function
        }
    });
}

จากนั้นคุณเพียงเชื่อมโยงวิธีการตามสัญญาที่ส่งคืน:

async function bar () {
    try {
        var result = await foo("someID")
        // use the result here
    } catch (error) {
        // handle error here
    }
}

bar()

แหล่งที่มา - บทช่วยสอนนี้:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise


5
คำถามที่ถามโดยเฉพาะเกี่ยวกับการใช้ async / รอ ไม่ได้ใช้สัญญา
Mak

คำตอบนี้ไม่ได้เป็นคำตอบที่ถูกต้องที่ชัดเจน นี่เป็นคำตอบสำหรับคำตอบอื่น ๆ ที่ให้ไว้ข้างต้น ฉันจะใส่มันลงไปในความคิดเห็น แต่เนื่องจากฉันมีรหัสฟิลด์คำตอบคือที่ที่ดีกว่า
OzzyTheGiant

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

ฉันได้แก้ไขการตอบกลับของคุณเพื่อให้อัปเดต แจ้งให้เราทราบหากฉันพลาดบางสิ่งบางอย่าง
Mak

4

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

import to from './to';

async foo(id: string): Promise<A> {
    let err, result;
    [err, result] = await to(someAsyncPromise()); // notice the to() here
    if (err) {
        return 400;
    }
    return 200;
}

ตำแหน่งที่ควรนำเข้าฟังก์ชันto.tsจาก:

export default function to(promise: Promise<any>): Promise<any> {
    return promise.then(data => {
        return [null, data];
    }).catch(err => [err]);
}

เครดิตไป Dima กรอสแมนในต่อไปนี้การเชื่อมโยง


1
ผมใช้การก่อสร้างนี้เกือบเฉพาะ (ทำความสะอาดมาก) และมีความเป็น 'เป็น' โมดูลที่ได้รับรอบสำหรับประเดี๋ยวnpmjs.com/package/await-to-js ไม่จำเป็นต้องมีการประกาศแยกต่างหากเพียงแค่ปล่อยให้อยู่ต่อหน้างานที่ได้รับมอบหมาย นอกจากนี้ยังสามารถทำได้เพียงแค่ let [err]=ตรวจสอบข้อผิดพลาด
DKebler

3

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

หากรหัสของคุณกำลังใช้async/ awaitก็เป็นวิธีที่ดีในการปฏิเสธด้วยErrorแทนที่จะเป็น400:

try {
  await foo('a');
}
catch (e) {
  // you would still want `e` to be an `Error` instead of `400`
}

3

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

เพื่อให้แน่ใจว่าฉันไม่ได้แยกเส้นขนออกไปฉันจึงทำการทดสอบประสิทธิภาพของวิธีการต่าง ๆ สามวิธีโดยใช้รหัสนี้:

const iterations = 100000;

function getSwitch() {
  return Math.round(Math.random()) === 1;
}

function doSomething(value) {
  return 'something done to ' + value.toString();
}

let processWithThrow = function () {
  if (getSwitch()) {
    throw new Error('foo');
  }
};

let processWithReturn = function () {
  if (getSwitch()) {
    return new Error('bar');
  } else {
    return {}
  }
};

let processWithCustomObject = function () {
  if (getSwitch()) {
    return {type: 'rejection', message: 'quux'};
  } else {
    return {type: 'usable response', value: 'fnord'};
  }
};

function testTryCatch(limit) {
  for (let i = 0; i < limit; i++) {
    try {
      processWithThrow();
    } catch (e) {
      const dummyValue = doSomething(e);
    }
  }
}

function testReturnError(limit) {
  for (let i = 0; i < limit; i++) {
    const returnValue = processWithReturn();
    if (returnValue instanceof Error) {
      const dummyValue = doSomething(returnValue);
    }
  }
}

function testCustomObject(limit) {
  for (let i = 0; i < limit; i++) {
    const returnValue = processWithCustomObject();
    if (returnValue.type === 'rejection') {
      const dummyValue = doSomething(returnValue);
    }
  }
}

let start, end;
start = new Date();
testTryCatch(iterations);
end = new Date();
const interval_1 = end - start;
start = new Date();
testReturnError(iterations);
end = new Date();
const interval_2 = end - start;
start = new Date();
testCustomObject(iterations);
end = new Date();
const interval_3 = end - start;

console.log(`with try/catch: ${interval_1}ms; with returned Error: ${interval_2}ms; with custom object: ${interval_3}ms`);

บางสิ่งที่อยู่ในนั้นรวมอยู่ด้วยเนื่องจากความไม่แน่นอนของฉันเกี่ยวกับล่าม Javascript (ฉันแค่ต้องการลงกระต่ายทีละช่อง) เช่นฉันรวมdoSomethingฟังก์ชั่นและมอบหมายให้กลับไปdummyValueเพื่อให้แน่ใจว่าบล็อกตามเงื่อนไขจะไม่ได้รับการปรับให้เหมาะสม

ผลลัพธ์ของฉันคือ:

with try/catch: 507ms; with returned Error: 260ms; with custom object: 5ms

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

ดังนั้น…ในขณะที่ฉันคิดว่าแนวทางของคำตอบที่ยอมรับนั้นดีในกรณีที่คุณคาดหวังว่าจะต้องจัดการกับข้อผิดพลาดที่ไม่สามารถคาดเดาได้ในฟังก์ชั่น async ในกรณีที่การปฏิเสธนั้นหมายถึง "คุณต้องไปกับแผน B C หรือ D …) "ฉันคิดว่าการตั้งค่าของฉันจะปฏิเสธการใช้วัตถุตอบกลับที่กำหนดเอง


2
นอกจากนี้โปรดจำไว้ว่าคุณไม่จำเป็นต้องเครียดเกี่ยวกับการจัดการข้อผิดพลาดที่ไม่คาดคิดภายในฟังก์ชัน async หากการเรียกไปยังฟังก์ชันนั้นอยู่ในบล็อกลอง / จับในขอบเขตการล้อมตั้งแต่ - ซึ่งแตกต่างจากฟังก์ชั่น Promises - การล้อมรอบขอบเขตซึ่งมีการจัดการเช่นเดียวกับข้อผิดพลาดภายในขอบเขตนั้น นั่นเป็นหนึ่งในข้อได้เปรียบหลักของการซิงค์ / รอคอย!
RiqueW

Microbenchmarks เป็นปีศาจ ดูตัวเลขให้ละเอียดยิ่งขึ้น คุณต้องทำอะไรซักอย่าง 1,000x เพื่อสังเกตความแตกต่าง 1ms ที่นี่ ใช่การเพิ่มการโยน / จับจะทำให้ฟังก์ชั่นลดลง แต่ก) หากคุณกำลังรอบางสิ่งบางอย่างแบบอะซิงก์ก็น่าจะใช้คำสั่งหลายขนาดใช้เวลานานกว่า 0.0005 Ms เพื่อให้เกิดขึ้นในพื้นหลัง b) คุณต้องทำ 1,000x เพื่อสร้างความแตกต่าง 1ms ที่นี่
Jamie Pate
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.