รอจนกว่าสัญญาทั้งหมดจะเสร็จสิ้นแม้ว่าบางคนจะถูกปฏิเสธ


405

สมมติว่าฉันมีชุดของPromiseที่กำลังร้องขอเครือข่ายซึ่งจะล้มเหลว:

// http://does-not-exist will throw a TypeError
var arr = [ fetch('index.html'), fetch('http://does-not-exist') ]

Promise.all(arr)
  .then(res => console.log('success', res))
  .catch(err => console.log('error', err)) // This is executed   

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

เนื่องจากPromises.allไม่ได้ออกจากห้องนี้สิ่งที่เป็นรูปแบบที่แนะนำสำหรับการจัดการนี้โดยไม่ต้องใช้ห้องสมุดสัญญา?


สิ่งที่ควรส่งคืนในอาร์เรย์ผลลัพธ์สำหรับสัญญาที่ถูกปฏิเสธ
Kuba Wyrostek

9
ES6 สัญญาว่าจะไม่สนับสนุนวิธีดังกล่าว (และเห็นได้ชัดว่าช้ากว่า Bluebird ) นอกจากนี้เบราว์เซอร์หรือเอนจิ้นบางตัวเท่านั้นที่สนับสนุนพวกเขา ฉันขอแนะนำอย่างยิ่งให้ใช้ Bluebird ซึ่งมาพร้อมกับallSettledความต้องการของคุณโดยที่คุณไม่ต้องม้วนเอง
Dan Pantry

@ KubaWyrostek ฉันคิดว่าคุณทำให้เหตุผลที่สัญญาไว้ทั้งหมดไม่ได้มีพฤติกรรมนี้ซึ่งฉันคิดว่าเหมาะสม นี่ไม่ใช่วิธีการทำงาน แต่มุมมองทางเลือกคือการบอกว่า Promise.all ควรส่งคืนสัญญาพิเศษที่ไม่เคยล้มเหลว - และคุณจะได้รับข้อผิดพลาดที่ถูกส่งออกไปเนื่องจากข้อโต้แย้งที่แสดงถึงสัญญาที่ล้มเหลว
Nathan Hagen

หากต้องการเพิ่มสิ่งที่ Dan แชร์ไว้ฟังก์ชันการทำงานแบบ AllSettled / settlementAll ที่ Bluebird สามารถใช้ผ่านฟังก์ชั่น "reflect"
user3344977

2
@Coli: อืมฉันไม่คิดอย่างนั้น Promise.allจะปฏิเสธทันทีที่สัญญาใดสัญญาหนึ่งปฏิเสธดังนั้นสำนวนที่เสนอของคุณไม่รับประกันว่าสัญญาทั้งหมดจะได้รับการตัดสิน
Jörg W Mittag

คำตอบ:


309

อัปเดตคุณอาจต้องการใช้เนทิฟในตัวPromise.allSettled:

Promise.allSettled([promise]).then(([result]) => {
   //reach here regardless
   // {status: "fulfilled", value: 33}
});

ในความเป็นจริงแล้วคำตอบด้านล่างนี้เป็นงานศิลปะก่อนหน้าในการเพิ่มวิธีดังกล่าวในภาษา:]


แน่นอนคุณต้องreflect:

const reflect = p => p.then(v => ({v, status: "fulfilled" }),
                            e => ({e, status: "rejected" }));

reflect(promise).then((v => {
    console.log(v.status);
});

หรือกับ ES5:

function reflect(promise){
    return promise.then(function(v){ return {v:v, status: "fulfilled" }},
                        function(e){ return {e:e, status: "rejected" }});
}


reflect(promise).then(function(v){
    console.log(v.status);
});

หรือในตัวอย่างของคุณ:

var arr = [ fetch('index.html'), fetch('http://does-not-exist') ]

Promise.all(arr.map(reflect)).then(function(results){
    var success = results.filter(x => x.status === "fulfilled");
});

3
ฉันคิดว่านี่เป็นทางออกที่ดี คุณสามารถแก้ไขเพื่อรวมไวยากรณ์ที่ง่ายขึ้นได้หรือไม่ ปมของปัญหาคือถ้าคุณต้องการจัดการข้อผิดพลาดในสัญญาย่อยคุณควรจับพวกเขาและส่งกลับข้อผิดพลาด ตัวอย่างเช่น: gist.github.com/nhagen/a1d36b39977822c224b8
Nathan Hagen

3
@NathanHagen ช่วยให้คุณทราบว่ามีอะไรที่ถูกปฏิเสธและสิ่งใดที่ได้รับการเติมเต็มและแยกปัญหาออกเป็นตัวดำเนินการที่นำกลับมาใช้ใหม่ได้
Benjamin Gruenbaum

4
เพื่อเป็นการตอบสนองต่อปัญหาของฉันฉันได้สร้างแพ็กเกจ npm ต่อไปนี้: github.com/Bucabug/promise-reflect npmjs.com/package/promise-reflect
SamF

2
ฉันพบปัญหานี้เมื่อไม่นานมานี้และฉันได้สร้างแพคเกจ npm สำหรับมัน: npmjs.com/package/promise-all-soft-fail
velocity_distance

5
คำนี้reflectเป็นคำทั่วไปในวิทยาศาสตร์คอมพิวเตอร์หรือไม่? คุณช่วยกรุณาลิงค์ไปยังที่ซึ่งสิ่งนี้ถูกอธิบายเช่นเดียวกับในวิกิพีเดียหรืออะไรบางอย่าง ฉันค้นหาอย่างหนักPromise.all not even first rejectแต่ไม่รู้จะค้นหา "สะท้อน" ES6 ควรมีคำPromise.reflectว่า "Promise.all แต่จริงๆทั้งหมด" หรือไม่?
Noitidart

253

คำตอบที่คล้ายกัน แต่มีแนวโน้มมากกว่าสำหรับ ES6 อาจจะ:

const a = Promise.resolve(1);
const b = Promise.reject(new Error(2));
const c = Promise.resolve(3);

Promise.all([a, b, c].map(p => p.catch(e => e)))
  .then(results => console.log(results)) // 1,Error: 2,3
  .catch(e => console.log(e));


const console = { log: msg => div.innerHTML += msg + "<br>"};
<div id="div"></div>

ทั้งนี้ขึ้นอยู่กับประเภท (s) ของค่าส่งกลับข้อผิดพลาดที่มักจะสามารถประสบความสำเร็จได้อย่างง่ายดายพอ (เช่นการใช้งานundefinedสำหรับ "ไม่สนใจ" typeofสำหรับค่าที่ไม่ใช่วัตถุธรรมดาresult.message, result.toString().startsWith("Error:")ฯลฯ )


1
@KarlBateman ฉันคิดว่าคุณสับสน ฟังก์ชันการสั่งซื้อแก้ไขหรือปฏิเสธไม่สำคัญที่นี่เนื่องจาก.map(p => p.catch(e => e))ส่วนจะเปลี่ยนการปฏิเสธทั้งหมดเป็นค่าที่ได้รับการแก้ไขดังนั้นPromise.allยังคงรอให้ทุกอย่างเสร็จสิ้นว่าแต่ละฟังก์ชันจะแก้ไขหรือปฏิเสธโดยไม่คำนึงว่าจะใช้เวลานานเท่าใด ลองมัน.
jib

39
.catch(e => console.log(e));ไม่เคยถูกเรียกเพราะสิ่งนี้ไม่เคยล้มเหลว
Fregante

4
@ bfred.it ถูกต้อง แม้ว่าการยกเลิกสัญญากับเครือข่ายcatchโดยทั่วไปคือการปฏิบัติที่ดีIMHO
jib

2
@SuhailGupta มันจับข้อผิดพลาดeและส่งกลับเป็นค่าปกติ (สำเร็จ) เหมือนกับp.catch(function(e) { return e; })สั้นกว่าเท่านั้น returnเป็นนัย
jib

1
@JustinReusnow กล่าวถึงความคิดเห็นแล้ว แนวปฏิบัติที่ดีเสมอในการยกเลิกเชนในกรณีที่คุณเพิ่มรหัสในภายหลัง
jib

71

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

let a = new Promise((res, rej) => res('Resolved!')),
    b = new Promise((res, rej) => rej('Rejected!')),
    c = a.catch(e => { console.log('"a" failed.'); return e; }),
    d = b.catch(e => { console.log('"b" failed.'); return e; });

Promise.all([c, d])
  .then(result => console.log('Then', result)) // Then ["Resolved!", "Rejected!"]
  .catch(err => console.log('Catch', err));

Promise.all([a.catch(e => e), b.catch(e => e)])
  .then(result => console.log('Then', result)) // Then ["Resolved!", "Rejected!"]
  .catch(err => console.log('Catch', err));

เมื่อก้าวต่อไปอีกหนึ่งขั้นคุณสามารถเขียนตัวจัดการแบบทั่วไปที่มีลักษณะดังนี้:

const catchHandler = error => ({ payload: error, resolved: false });

จากนั้นคุณสามารถทำได้

> Promise.all([a, b].map(promise => promise.catch(catchHandler))
    .then(results => console.log(results))
    .catch(() => console.log('Promise.all failed'))
< [ 'Resolved!',  { payload: Promise, resolved: false } ]

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

const successHandler = result => ({ payload: result, resolved: true });

ดังนั้นตอนนี้คุณสามารถทำสิ่งนี้:

> Promise.all([a, b].map(result => result.then(successHandler).catch(catchHandler))
    .then(results => console.log(results.filter(result => result.resolved))
    .catch(() => console.log('Promise.all failed'))
< [ 'Resolved!' ]

จากนั้นเพื่อให้มันแห้งคุณจะได้รับคำตอบของเบนจามิน:

const reflect = promise => promise
  .then(successHandler)
  .catch(catchHander)

ตอนนี้ดูเหมือนว่าที่ไหน

> Promise.all([a, b].map(result => result.then(successHandler).catch(catchHandler))
    .then(results => console.log(results.filter(result => result.resolved))
    .catch(() => console.log('Promise.all failed'))
< [ 'Resolved!' ]

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

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

ตัวอย่างเช่นคุณอาจมีสิ่งนี้:

const a = Promise.resolve(new Error('Not beaking, just bad'));
const b = Promise.reject(new Error('This actually didnt work'));

สิ่งนี้จะไม่ถูกดักจับa.catchดังนั้น

> Promise.all([a, b].map(promise => promise.catch(e => e))
    .then(results => console.log(results))
< [ Error, Error ]

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

หากคุณต้องการจัดการข้อผิดพลาดอย่างสง่างามคุณสามารถจัดการกับข้อผิดพลาดเป็นค่าที่ไม่ได้กำหนด:

> Promise.all([a.catch(() => undefined), b.catch(() => undefined)])
    .then((results) => console.log('Known values: ', results.filter(x => typeof x !== 'undefined')))
< [ 'Resolved!' ]

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

const apiMethod = () => fetch()
  .catch(error => {
    console.log(error.message);
    throw error;
  });

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

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


1
@Benjamin ผมคิดว่า @ วิธีการแก้ปัญหาของนาธานเป็นอย่างตรงไปตรงมาและสำนวนสำหรับPromises ในขณะที่การreflectปรับปรุงการใช้รหัสซ้ำของคุณจะเป็นการสร้างนามธรรมอีกระดับหนึ่ง เนื่องจากคำตอบของนาธานนั้นได้รับเพียง upvotes เพียงเศษเสี้ยวเมื่อเปรียบเทียบกับของคุณฉันสงสัยว่านี่เป็นตัวบ่งชี้ถึงปัญหาของการแก้ปัญหาของเขาซึ่งฉันยังไม่ได้รับการยอมรับ

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

2
@BenjaminGruenbaum ตัวอย่างเช่นnew Promise((res, rej) => res(new Error('Legitimate error'))จะไม่แยกความแตกต่างจากnew Promise(((res, rej) => rej(new Error('Illegitimate error'))? หรือต่อไปคุณจะไม่สามารถที่จะกรองตามx.status? ฉันจะเพิ่มจุดนี้ในคำตอบของฉันดังนั้นความแตกต่างที่ชัดเจนยิ่งขึ้น
นาธานฮาเกน

3
เหตุผลนี้เป็นความคิดที่ไม่ดีเพราะมันผูกติดอยู่กับการใช้งานสัญญาเฉพาะกรณีที่เคยถูกใช้ในPromise.all()ตัวแปรเฉพาะเท่านั้นมันก็จะกลายเป็นหน้าที่ของผู้บริโภคที่สัญญาว่าจะรู้ว่าสัญญาเฉพาะจะไม่ปฏิเสธ แต่จะ กลืนมันเป็นข้อผิดพลาด ในความเป็นจริงreflect()วิธีการนี้สามารถทำให้ 'นามธรรม' น้อยลงและชัดเจนขึ้นโดยเรียกมันว่าความPromiseEvery(promises).then(...)ซับซ้อนของคำตอบข้างต้นเมื่อเทียบกับของเบนจามินควรพูดเกี่ยวกับการแก้ปัญหานี้มาก
Neil

33

มีเป็นข้อเสนอสำเร็จรูปสำหรับฟังก์ชั่นที่สามารถบรรลุนี้กำเนิดในวานิลลา Javascript: Promise.allSettledซึ่งได้ทำให้มันไป 4 ขั้นตอนคือ officialized ใน ES2020 และจะดำเนินการในสภาพแวดล้อมที่ทันสมัย มันคล้ายกับreflectฟังก์ชั่นในคำตอบอื่น ๆนี้ นี่คือตัวอย่างจากหน้าข้อเสนอ ก่อนหน้านี้คุณต้องทำ:

function reflect(promise) {
  return promise.then(
    (v) => {
      return { status: 'fulfilled', value: v };
    },
    (error) => {
      return { status: 'rejected', reason: error };
    }
  );
}

const promises = [ fetch('index.html'), fetch('https://does-not-exist/') ];
const results = await Promise.all(promises.map(reflect));
const successfulPromises = results.filter(p => p.status === 'fulfilled');

ใช้Promise.allSettledแทนข้างต้นจะเทียบเท่ากับ:

const promises = [ fetch('index.html'), fetch('https://does-not-exist/') ];
const results = await Promise.allSettled(promises);
const successfulPromises = results.filter(p => p.status === 'fulfilled');

ผู้ที่ใช้สภาพแวดล้อมที่ทันสมัยจะสามารถใช้วิธีนี้ได้โดยไม่ต้องห้องสมุดใดในตัวอย่างข้อมูลต่อไปนี้ควรทำงานโดยไม่มีปัญหา:

Promise.allSettled([
  Promise.resolve('a'),
  Promise.reject('b')
])
  .then(console.log);

เอาท์พุท:

[
  {
    "status": "fulfilled",
    "value": "a"
  },
  {
    "status": "rejected",
    "reason": "b"
  }
]

เบราว์เซอร์รุ่นเก่าที่มีสเปคที่สอดคล้อง polyfill ที่นี่


1
มันคือด่าน 4 และน่าจะลงจอดใน ES2020
Estus Flask

มีให้บริการในโหนด 12 :)
Callum M

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

9

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

Promise.settle = function(promises) {
  var results = [];
  var done = promises.length;

  return new Promise(function(resolve) {
    function tryResolve(i, v) {
      results[i] = v;
      done = done - 1;
      if (done == 0)
        resolve(results);
    }

    for (var i=0; i<promises.length; i++)
      promises[i].then(tryResolve.bind(null, i), tryResolve.bind(null, i));
    if (done == 0)
      resolve(results);
  });
}

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

2
ตกลงการชำระจะเป็นชื่อที่ดีกว่าแน่นอน :)
Kuba Wyrostek

ดูเหมือนว่าสัญญาการก่อสร้างที่ชัดเจนสัญญา ควรสังเกตว่าคุณไม่ควรเขียนฟังก์ชั่นดังกล่าวด้วยตัวคุณเอง แต่ใช้งานอุปกรณ์ห้องสมุดของคุณ (ตกลง ES6 ดั้งเดิมนั้นค่อนข้างน้อย)
Bergi

คุณช่วยกรุณาใช้ตัวPromiseสร้างได้อย่างถูกต้อง (และหลีกเลี่ยงสิ่งที่var resolve)
Bergi

Bergi อย่าลังเลที่จะเปลี่ยนคำตอบ แต่คุณคิดว่าจำเป็น
Kuba Wyrostek

5
var err;
Promise.all([
    promiseOne().catch(function(error) { err = error;}),
    promiseTwo().catch(function(error) { err = error;})
]).then(function() {
    if (err) {
        throw err;
    }
});

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


1
ดูเหมือนว่าสิ่งนี้จะรวม errs โดยทำให้อาร์เรย์และใช้err.push(error)ดังนั้นข้อผิดพลาดทั้งหมดจะเดือดปุด ๆ
ps2goat

4

ฉันมีปัญหาเดียวกันและแก้ไขมันด้วยวิธีต่อไปนี้:

const fetch = (url) => {
  return node-fetch(url)
    .then(result => result.json())
    .catch((e) => {
      return new Promise((resolve) => setTimeout(() => resolve(fetch(url)), timeout));
    });
};

tasks = [fetch(url1), fetch(url2) ....];

Promise.all(tasks).then(......)

ในกรณีPromise.allนั้นจะรอสัญญาทุกครั้งที่เข้ามาresolvedหรือrejectedรัฐ

และการมีทางออกนี้ทำให้เรา "หยุดcatchการทำงาน" ในลักษณะที่ไม่มีการบล็อก ในความเป็นจริงเราไม่ได้หยุดอะไรเลยเราเพียงแค่คืนกลับสู่Promiseสถานะรอดำเนินการซึ่งจะส่งคืนอีกPromiseเมื่อได้รับการแก้ไขหลังจากหมดเวลา


Promise.allแต่ที่จะเรียกทุกสัญญาที่จะเมื่อคุณเรียกใช้ ฉันกำลังมองหาวิธีที่จะฟังเมื่อสัญญาทั้งหมดถูกเรียกใช้ แต่ไม่ได้เรียกใช้พวกเขาเอง ขอบคุณ
SudoPlz

@SudoPlz วิธีการall()ทำเช่นนั้นจะรอการปฏิบัติตามสัญญาทั้งหมดหรือปฏิเสธอย่างน้อยหนึ่งมัน
user1016265

นั่นเป็นความจริง แต่มันไม่เพียงรอ แต่จริงๆแล้วมันเรียกใช้ / เริ่มต้น / ดับกระบวนการ ถ้าคุณอยากที่จะยิงขึ้นสัญญาบางแห่งอื่นไม่ว่าจะเป็นไปได้ becase .allไฟทุกอย่างขึ้น
SudoPlz

@SudoPlz หวังว่าสิ่งนี้จะเปลี่ยนความคิดเห็นของคุณjsfiddle.net/d1z1vey5
user1016265

3
ฉันยืนแก้ไขแล้ว จนถึงตอนนี้ฉันคิดว่าสัญญาจะทำงานเฉพาะเมื่อมีคนเรียกใช้พวกเขา ( thenหรือที่.allเรียกว่าโทร) แต่พวกเขาก็ทำงานเมื่อสร้างขึ้น
SudoPlz


2

Benjamin Gruenbaum คำตอบนั้นยอดเยี่ยมมาก แต่ฉันสามารถเห็นได้ว่านาธานฮาเก็นมองด้วยระดับความเป็นนามธรรมที่คลุมเครือ การมีคุณสมบัติของวัตถุระยะสั้นอย่างเช่นe & vไม่ช่วยอะไร แต่แน่นอนว่าสามารถเปลี่ยนแปลงได้

ใน Javascript มีวัตถุข้อผิดพลาดมาตรฐานที่เรียกว่าError, เป็นการดีที่คุณจะโยนอินสแตนซ์ / ลูกหลานของสิ่งนี้ ข้อดีคือคุณสามารถทำได้instanceof Errorและคุณรู้ว่ามีข้อผิดพลาด

ดังนั้นการใช้ความคิดนี้นี่คือปัญหาของฉัน

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

instanceof ภายในจับเป็นในกรณีที่คุณใช้บางห้องสมุดภายนอกที่อาจจะทำแทนreject("error")reject(new Error("error"))

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

ข้อดีอีกอย่างของการทำเช่นนี้คือการทำลายล้างอาร์เรย์ทำได้ง่าย ๆ

const [value1, value2] = PromiseAllCatch(promises);
if (!(value1 instanceof Error)) console.log(value1);

แทน

const [{v: value1, e: error1}, {v: value2, e: error2}] = Promise.all(reflect..
if (!error1) { console.log(value1); }

คุณสามารถยืนยันว่าการ!error1ตรวจสอบนั้นง่ายกว่าอินสแตนซ์ของคุณ แต่คุณต้องทำลายทั้งคู่v & eด้วย

function PromiseAllCatch(promises) {
  return Promise.all(promises.map(async m => {
    try {
      return await m;
    } catch(e) {
      if (e instanceof Error) return e;
      return new Error(e);
    }
  }));
}


async function test() {
  const ret = await PromiseAllCatch([
    (async () => "this is fine")(),
    (async () => {throw new Error("oops")})(),
    (async () => "this is ok")(),
    (async () => {throw "Still an error";})(),
    (async () => new Error("resolved Error"))(),
  ]);
  console.log(ret);
  console.log(ret.map(r =>
    r instanceof Error ? "error" : "ok"
    ).join(" : ")); 
}

test();


2

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

const promise = arg => {
  return new Promise((resolve, reject) => {
      setTimeout(() => {
        try{
          if(arg != 2)
            return resolve({success: true, data: arg});
          else
            throw new Error(arg)
        }catch(e){
          return resolve({success: false, error: e, data: arg})
        }
      }, 1000);
  })
}

Promise.all([1,2,3,4,5].map(e => promise(e))).then(d => console.log(d))


1
นี่เป็นงานที่ดีไม่สวยงาม แต่จะใช้งานได้ดี
Sunny Tambi

1

ฉันคิดว่าสิ่งต่อไปนี้มีวิธีการที่แตกต่างกันเล็กน้อย ... เปรียบเทียบfn_fast_fail()กับfn_slow_fail()... แม้ว่าหลังจะไม่ล้มเหลวเช่นนี้ ... คุณสามารถตรวจสอบว่าหนึ่งหรือทั้งสองaและbเป็นตัวอย่างของErrorและthrowที่Errorถ้าคุณต้องการที่จะเข้าถึงcatchบล็อก (เช่นif (b instanceof Error) { throw b; }) ดูjsfiddle

var p1 = new Promise((resolve, reject) => { 
    setTimeout(() => resolve('p1_delayed_resolvement'), 2000); 
}); 

var p2 = new Promise((resolve, reject) => {
    reject(new Error('p2_immediate_rejection'));
});

var fn_fast_fail = async function () {
    try {
        var [a, b] = await Promise.all([p1, p2]);
        console.log(a); // "p1_delayed_resolvement"
        console.log(b); // "Error: p2_immediate_rejection"
    } catch (err) {
        console.log('ERROR:', err);
    }
}

var fn_slow_fail = async function () {
    try {
        var [a, b] = await Promise.all([
            p1.catch(error => { return error }),
            p2.catch(error => { return error })
        ]);
        console.log(a); // "p1_delayed_resolvement"
        console.log(b); // "Error: p2_immediate_rejection"
    } catch (err) {
        // we don't reach here unless you throw the error from the `try` block
        console.log('ERROR:', err);
    }
}

fn_fast_fail(); // fails immediately
fn_slow_fail(); // waits for delayed promise to resolve

0

นี่คือประเพณีของฉัน settledPromiseAll()

const settledPromiseAll = function(promisesArray) {
  var savedError;

  const saveFirstError = function(error) {
    if (!savedError) savedError = error;
  };
  const handleErrors = function(value) {
    return Promise.resolve(value).catch(saveFirstError);
  };
  const allSettled = Promise.all(promisesArray.map(handleErrors));

  return allSettled.then(function(resolvedPromises) {
    if (savedError) throw savedError;
    return resolvedPromises;
  });
};

เปรียบเทียบกับ Promise.all

  • หากสัญญาทั้งหมดได้รับการแก้ไขสัญญาจะดำเนินการเหมือนกับสัญญามาตรฐาน

  • หากหนึ่งในสัญญาถูกปฏิเสธมันจะส่งกลับหนึ่งสัญญาแรกที่ปฏิเสธมากเช่นเดียวกับมาตรฐาน แต่ไม่เหมือนที่รอสัญญาทั้งหมดเพื่อแก้ไข / ปฏิเสธ

สำหรับผู้กล้าเราสามารถเปลี่ยนPromise.all():

(function() {
  var stdAll = Promise.all;

  Promise.all = function(values, wait) {
    if(!wait)
      return stdAll.call(Promise, values);

    return settledPromiseAll(values);
  }
})();

ระวัง โดยทั่วไปแล้วเราไม่เคยเปลี่ยนบิวด์อินเนื่องจากอาจทำลายไลบรารี JS ที่ไม่เกี่ยวข้องหรือการปะทะกันกับการเปลี่ยนแปลงมาตรฐาน JS ในอนาคต

ฉันsettledPromiseallเข้ากันได้กับPromise.allและขยายการทำงานของมัน

ผู้ที่กำลังพัฒนามาตรฐาน - ทำไมไม่รวมสิ่งนี้กับมาตรฐานสัญญาใหม่


0

Promise.allด้วยการใช้async/awaitวิธีการที่ทันสมัย

const promise1 = //...
const promise2 = //...

const data = await Promise.all([promise1, promise2])

const dataFromPromise1 = data[0]
const dataFromPromise2 = data[1]

-1

ฉันจะทำ:

var err = [fetch('index.html').then((success) => { return Promise.resolve(success); }).catch((e) => { return Promise.resolve(e); }),
fetch('http://does-not-exist').then((success) => { return Promise.resolve(success); }).catch((e) => { return Promise.resolve(e); })];

Promise.all(err)
.then(function (res) { console.log('success', res) })
.catch(function (err) { console.log('error', err) }) //never executed

-1

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

function synchronousCode() {
    function myFetch(url) {
        try {
            return window.fetch(url).data;
        }
        catch (e) {
            return {status: 'failed:'+e};
        };
    };
    var arr=[
        myFetch("https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"),
        myFetch("https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/NONEXISTANT.js"),
        myFetch("https://ajax.NONEXISTANT123.com/ajax/libs/jquery/2.0.0/NONEXISTANT.js")
    ];
    
    console.log('array is ready:',arr[0].status,arr[1].status,arr[2].status);
};

nsynjs.run(synchronousCode,{},function(){
    console.log('done');
});
<script src="https://rawgit.com/amaksr/nsynjs/master/nsynjs.js"></script>


-1

ฉันใช้รหัสต่อไปนี้มาตั้งแต่ ES5

Promise.wait = function(promiseQueue){
    if( !Array.isArray(promiseQueue) ){
        return Promise.reject('Given parameter is not an array!');
    }

    if( promiseQueue.length === 0 ){
        return Promise.resolve([]);
    }

    return new Promise((resolve, reject) =>{
        let _pQueue=[], _rQueue=[], _readyCount=false;
        promiseQueue.forEach((_promise, idx) =>{
            // Create a status info object
            _rQueue.push({rejected:false, seq:idx, result:null});
            _pQueue.push(Promise.resolve(_promise));
        });

        _pQueue.forEach((_promise, idx)=>{
            let item = _rQueue[idx];
            _promise.then(
                (result)=>{
                    item.resolved = true;
                    item.result = result;
                },
                (error)=>{
                    item.resolved = false;
                    item.result = error;
                }
            ).then(()=>{
                _readyCount++;

                if ( _rQueue.length === _readyCount ) {
                    let result = true;
                    _rQueue.forEach((item)=>{result=result&&item.resolved;});
                    (result?resolve:reject)(_rQueue);
                }
            });
        });
    });
};

Promise.allลายเซ็นการใช้งานเช่นเดียวกับ ความแตกต่างที่สำคัญคือว่าPromise.waitจะรอให้สัญญาทั้งหมดเพื่อให้งานของพวกเขาเสร็จ


-1

ฉันรู้ว่าคำถามนี้มีคำตอบมากมายและฉันแน่ใจว่าต้อง (ถ้าไม่ทั้งหมด) ถูกต้อง อย่างไรก็ตามมันยากสำหรับฉันที่จะเข้าใจตรรกะ / การไหลของคำตอบเหล่านี้

ดังนั้นฉันจึงดูที่การนำไปปฏิบัติดั้งเดิมPromise.all()และฉันพยายามเลียนแบบตรรกะนั้น - ยกเว้นว่าจะไม่หยุดการทำงานหากสัญญาหนึ่งล้มเหลว

  public promiseExecuteAll(promisesList: Promise<any>[] = []): Promise<{ data: any, isSuccess: boolean }[]>
  {
    let promise: Promise<{ data: any, isSuccess: boolean }[]>;

    if (promisesList.length)
    {
      const result: { data: any, isSuccess: boolean }[] = [];
      let count: number = 0;

      promise = new Promise<{ data: any, isSuccess: boolean }[]>((resolve, reject) =>
      {
        promisesList.forEach((currentPromise: Promise<any>, index: number) =>
        {
          currentPromise.then(
            (data) => // Success
            {
              result[index] = { data, isSuccess: true };
              if (promisesList.length <= ++count) { resolve(result); }
            },
            (data) => // Error
            {
              result[index] = { data, isSuccess: false };
              if (promisesList.length <= ++count) { resolve(result); }
            });
        });
      });
    }
    else
    {
      promise = Promise.resolve([]);
    }

    return promise;
  }

คำอธิบาย:
- วนซ้ำอินพุตpromisesListและดำเนินการแต่ละสัญญา
- ไม่ว่าสัญญาได้รับการแก้ไขหรือปฏิเสธ: บันทึกผลของสัญญาในอาร์เรย์ตามresult indexบันทึกสถานะการแก้ไข / ปฏิเสธ ( isSuccess)
- เมื่อสัญญาทั้งหมดเสร็จสมบูรณ์ให้ส่งคืนหนึ่งสัญญาพร้อมผลลัพธ์ของคนอื่น ๆ ทั้งหมด

ตัวอย่างการใช้งาน:

const p1 = Promise.resolve("OK");
const p2 = Promise.reject(new Error(":-("));
const p3 = Promise.resolve(1000);

promiseExecuteAll([p1, p2, p3]).then((data) => {
  data.forEach(value => console.log(`${ value.isSuccess ? 'Resolve' : 'Reject' } >> ${ value.data }`));
});

/* Output: 
Resolve >> OK
Reject >> :-(
Resolve >> 1000
*/

2
อย่าพยายามนำไปใช้ใหม่Promise.allด้วยตนเองมีหลายสิ่งที่จะผิดพลาด เวอร์ชันของคุณไม่ได้จัดการอินพุตว่างเช่น
Bergi

-4

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

แก้ไข: ตกลงเนื่องจากคุณต้องการใช้ ES6 ธรรมดาที่ไม่มีไลบรารีภายนอกจึงไม่มีวิธีดังกล่าว

กล่าวอีกนัยหนึ่ง: คุณต้องวนซ้ำสัญญาของคุณด้วยตนเองและแก้ไขสัญญารวมใหม่ทันทีที่มีการชำระสัญญาทั้งหมด


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