Đợi cho đến khi tất cả các lời hứa hoàn thành ngay cả khi một số bị từ chối


405

Giả sử tôi có một bộ Promises đang thực hiện các yêu cầu mạng, trong đó một yêu cầu sẽ thất bại:

// 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   

Hãy nói rằng tôi muốn đợi cho đến khi tất cả những điều này kết thúc, bất kể nếu một lần thất bại. Có thể có lỗi mạng đối với tài nguyên mà tôi có thể sống mà không có, nhưng nếu tôi có thể nhận được, tôi muốn trước khi tiếp tục. Tôi muốn xử lý các lỗi mạng một cách duyên dáng.

Promises.allkhông để lại bất kỳ chỗ nào cho việc này, mô hình được đề xuất để xử lý việc này là gì, mà không sử dụng thư viện hứa hẹn?


Điều gì sẽ được trả lại trong mảng kết quả cho những lời hứa bị từ chối?
Kuba Wyrostek

9
ES6 hứa hẹn không hỗ trợ phương pháp nào như vậy (và hiện tại dường như chậm hơn Bluebird ). Ngoài ra, không phải tất cả các trình duyệt hoặc công cụ hỗ trợ chúng. Tôi sẽ mạnh mẽ khuyên bạn sử dụng Bluebird, mà đi kèm với allSettledđó đáp ứng nhu cầu của bạn mà không cần phải cuộn của riêng bạn.
Dan Pantry

@KubaWyrostek Tôi nghĩ rằng bạn đưa ra lý do Promise.all không có hành vi này, mà tôi nghĩ có ý nghĩa. Đây không phải là cách nó hoạt động, nhưng một quan điểm khác sẽ nói Promise.all sẽ trả lại một lời hứa đặc biệt không bao giờ thất bại - và bạn sẽ nhận được lỗi được đưa ra như là đối số thể hiện lời hứa thất bại.
Nathan Hagen

Để thêm vào những gì Dan đã chia sẻ, chức năng allSettled / giải quyết Tất cả giống như bluebird có thể được sử dụng thông qua chức năng "phản chiếu".
dùng3344977

2
@Coli: Hmm, tôi không nghĩ vậy. Promise.allsẽ từ chối ngay khi bất kỳ lời hứa nào từ chối, vì vậy thành ngữ được đề xuất của bạn không đảm bảo rằng tất cả các lời hứa đã được giải quyết.
Jörg W Mittag

Câu trả lời:


309

Cập nhật, bạn có thể muốn sử dụng bản địa tích hợp Promise.allSettled:

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

Như một sự thật thú vị, câu trả lời dưới đây là nghệ thuật trước khi thêm phương pháp đó vào ngôn ngữ:]


Chắc chắn, bạn chỉ cần một reflect:

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

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

Hoặc với 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);
});

Hoặc trong ví dụ của bạn:

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
Tôi nghĩ rằng đây là một giải pháp tuyệt vời. Bạn có thể sửa đổi nó để bao gồm một cú pháp đơn giản hơn? Mấu chốt của vấn đề là nếu bạn muốn xử lý lỗi trong các lời hứa phụ, bạn nên bắt chúng và trả lại lỗi. Vì vậy, ví dụ: gist.github.com/nhagen/a1d36b39977822c224b8
Nathan Hagen

3
@NathanHagen nó cho phép bạn tìm ra những gì bị từ chối và những gì đã hoàn thành và giải quyết vấn đề cho một nhà điều hành có thể sử dụng lại.
Benjamin Gruenbaum

4
Để đối phó với vấn đề riêng của tôi, tôi đã tạo ra các gói NPM sau: github.com/Bucabug/promise-reflect npmjs.com/package/promise-reflect
SamF

2
Tôi đã gặp phải vấn đề này cách đây một thời gian và tôi đã tạo gói npm
velocity_distance

5
Là từ reflectmột từ phổ biến trong khoa học máy tính? Bạn có thể vui lòng liên kết đến nơi mà điều này được giải thích như trên wikipedia hoặc một cái gì đó. Tôi đã tìm kiếm rất nhiều Promise.all not even first rejectnhưng không biết tìm kiếm "Reflect". ES6 có nên có một Promise.reflectcái giống như "Promise.all nhưng thực sự là tất cả" không?
Noitidart

253

Câu trả lời tương tự, nhưng thành ngữ hơn cho ES6 có lẽ:

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>

Tùy thuộc vào loại (các) giá trị được trả về, lỗi thường có thể được phân biệt đủ dễ dàng (ví dụ: sử dụng undefinedcho "không quan tâm", typeofcho các giá trị không phải đối tượng đơn giản result.message, result.toString().startsWith("Error:")v.v.)


1
@KarlBHRan Tôi nghĩ bạn đang bối rối. Các hàm thứ tự giải quyết hoặc từ chối không quan trọng ở đây vì .map(p => p.catch(e => e))phần này biến tất cả các từ chối thành các giá trị được giải quyết, do đó, Promise.allvẫn chờ mọi thứ kết thúc cho dù các hàm riêng lẻ giải quyết hay từ chối, bất kể chúng mất bao lâu. Thử nó.
jib

39
.catch(e => console.log(e));không bao giờ được gọi bởi vì điều này không bao giờ thất bại
fregante

4
@ bfred.it Đúng vậy. Mặc dù chấm dứt chuỗi hứa hẹn với IMHOcatch thường là thực hành tốt .
jib

2
@SuhailGupta Nó bắt lỗi evà trả về dưới dạng giá trị (thành công) thông thường. Tương tự như p.catch(function(e) { return e; })chỉ ngắn hơn. returnlà ngầm.
jib

1
@JustinReusnow đã được bình luận. Luôn luôn thực hành tốt để chấm dứt chuỗi trong trường hợp bạn thêm mã sau này.
jib

71

Câu trả lời của Benjamin cung cấp một sự trừu tượng tuyệt vời để giải quyết vấn đề này, nhưng tôi đã hy vọng cho một giải pháp ít trừu tượng hơn. Cách rõ ràng để giải quyết vấn đề này là chỉ cần gọi .catchcác lời hứa nội bộ và trả lại lỗi từ cuộc gọi lại của họ.

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));

Tiến lên một bước nữa, bạn có thể viết một trình xử lý bắt chung chung trông như thế này:

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

sau đó bạn có thể làm

> 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 } ]

Vấn đề với điều này là các giá trị bị bắt sẽ có giao diện khác với các giá trị không bị bắt, do đó, để làm sạch điều này, bạn có thể làm một cái gì đó như:

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

Vì vậy, bây giờ bạn có thể làm điều này:

> 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!' ]

Sau đó, để giữ cho nó KHÔ, bạn có được câu trả lời của Benjamin:

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

bây giờ nó trông như thế nào

> 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!' ]

Lợi ích của giải pháp thứ hai là trừu tượng hóa và DRY. Nhược điểm là bạn có nhiều mã hơn, và bạn phải nhớ để phản ánh tất cả các lời hứa của bạn để làm cho mọi thứ nhất quán.

Tôi sẽ mô tả giải pháp của mình là rõ ràng và HÔN, nhưng thực sự kém mạnh mẽ hơn. Giao diện không đảm bảo rằng bạn biết chính xác lời hứa thành công hay thất bại.

Ví dụ bạn có thể có cái này:

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

Điều này sẽ không bị bắt bởi a.catch, vì vậy

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

Không có cách nào để biết cái nào gây tử vong và cái nào không. Nếu điều đó quan trọng thì bạn sẽ muốn thực thi và giao diện theo dõi xem nó có thành công hay không (điều reflectnày).

Nếu bạn chỉ muốn xử lý lỗi một cách duyên dáng, thì bạn chỉ có thể coi lỗi là các giá trị không xác định:

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

Trong trường hợp của tôi, tôi không cần biết lỗi hoặc lỗi như thế nào - tôi chỉ quan tâm liệu tôi có giá trị hay không. Tôi sẽ để chức năng tạo ra lời hứa lo lắng về việc ghi lại lỗi cụ thể.

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

Bằng cách đó, phần còn lại của ứng dụng có thể bỏ qua lỗi của nó nếu muốn và coi nó là một giá trị không xác định nếu muốn.

Tôi muốn các chức năng cấp cao của mình thất bại một cách an toàn và không lo lắng về các chi tiết về lý do tại sao các phụ thuộc của nó không thành công và tôi cũng thích KISS hơn DRY khi tôi phải thực hiện sự đánh đổi đó - đó là lý do cuối cùng tôi chọn không sử dụng reflect.


1
@Benjamin Tôi nghĩ rằng giải pháp của @ Nathan rất đơn giản và thành ngữ cho Promises. Trong khi reflectcải thiện việc sử dụng lại mã của bạn , nó cũng thiết lập một mức độ trừu tượng khác. Vì câu trả lời của Nathan cho đến nay chỉ nhận được một phần nhỏ so với của bạn, tôi tự hỏi liệu đây có phải là dấu hiệu của một vấn đề với giải pháp của anh ấy không, mà tôi chưa nhận ra.

2
@ LUH3417 giải pháp này ít âm thanh hơn về mặt khái niệm vì nó coi các lỗi là các giá trị và không tách các lỗi khỏi các lỗi không. Ví dụ: nếu một trong những lời hứa giải quyết hợp pháp một giá trị có thể bị ném (điều này hoàn toàn có thể) thì điều này sẽ phá vỡ khá tệ.
Benjamin Gruenbaum

2
@BenjaminGruenbaum Chẳng hạn, new Promise((res, rej) => res(new Error('Legitimate error'))chẳng thể phân biệt được với new Promise(((res, rej) => rej(new Error('Illegitimate error'))? Hoặc xa hơn, bạn sẽ không thể lọc theo x.status? Tôi sẽ thêm điểm này vào câu trả lời của tôi để sự khác biệt rõ ràng hơn
Nathan Hagen

3
Lý do đây là một ý tưởng tồi là vì nó liên quan đến việc triển khai Promise với một trường hợp sử dụng cụ thể chỉ được sử dụng trong một Promise.all()biến thể cụ thể, sau đó nó cũng trở nên đương nhiên với người tiêu dùng Promise khi biết rằng một lời hứa cụ thể sẽ không từ chối nhưng sẽ nuốt lỗi. Trong thực tế, reflect()phương pháp có thể được thực hiện ít 'trừu tượng' hơn và rõ ràng hơn bằng cách gọi nó PromiseEvery(promises).then(...). Sự phức tạp của câu trả lời ở trên so với của Benjamin sẽ nói nhiều về giải pháp này.
Neil

33

Đã có một đề xuất hoàn chỉnh cho một chức năng có thể thực hiện điều này một cách tự nhiên, trong vanilla Javascript : Promise.allSettled, đã đưa nó đến giai đoạn 4, được chính thức hóa trong ES2020 và được triển khai trong mọi môi trường hiện đại . Nó rất giống với reflectchức năng trong câu trả lời khác này . Đây là một ví dụ, từ trang đề xuất. Trước đây, bạn sẽ phải làm:

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');

Sử dụng Promise.allSettledthay thế, ở trên sẽ tương đương với:

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

Những người sử dụng môi trường hiện đại sẽ có thể sử dụng phương pháp này mà không cần bất kỳ thư viện nào . Trong đó, đoạn mã sau sẽ chạy mà không gặp sự cố:

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

Đầu ra:

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

Đối với các trình duyệt cũ hơn, có một polyfill tuân thủ thông số kỹ thuật ở đây .


1
Đó là giai đoạn 4 và dự kiến ​​sẽ hạ cánh trong ES2020.
Bình Estus

Cũng có sẵn trong Nút 12 :)
Callum M

Ngay cả khi các câu trả lời khác vẫn còn hiệu lực, thì câu trả lời này sẽ nhận được nhiều sự ủng hộ hơn vì đó là cách giải quyết vấn đề mới nhất hiện nay.
Jacob

9

Tôi thực sự thích câu trả lời của Benjamin, và về cơ bản, anh ấy biến tất cả các lời hứa thành những quyết định luôn luôn giải quyết nhưng đôi khi có lỗi với kết quả như thế nào. :)
Đây là nỗ lực của tôi theo yêu cầu của bạn chỉ trong trường hợp bạn đang tìm kiếm giải pháp thay thế. Phương pháp này chỉ đơn giản coi các lỗi là kết quả hợp lệ và được mã hóa tương tự như Promise.allsau:

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);
  });
}

Điều này thường được gọi là settle. Chúng tôi cũng có điều đó trong bluebird, tôi thích phản ánh tốt hơn nhưng đây là một giải pháp khả thi khi bạn có điều này cho một mảng.
Benjamin Gruenbaum

2
OK, giải quyết sẽ là một cái tên tốt hơn thực sự. :)
Kuba Wyrostek

Điều này trông rất giống như antipotype xây dựng lời hứa rõ ràng. Cần lưu ý rằng bạn không bao giờ nên tự viết một chức năng như vậy, nhưng hãy sử dụng chức năng mà thư viện của bạn cung cấp (OK, ES6 bản địa hơi ít ỏi).
Bergi 16/07/2015

Bạn có thể vui lòng sử dụng các hàm Promisetạo đúng cách (và tránh điều đó var resolve) không?
Bergi 16/07/2015

Bergi, hãy thoải mái thay đổi câu trả lời tuy nhiên bạn thấy cần thiết.
Kuba Wyrostek 16/07/2015

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

Các Promise.allsẽ nuốt bất kỳ lời hứa từ chối và lưu trữ các lỗi trong một biến, vì vậy nó sẽ trở lại khi tất cả những lời hứa đã được giải quyết. Sau đó, bạn có thể ném lại lỗi, hoặc làm bất cứ điều gì. Bằng cách này, tôi đoán bạn sẽ nhận được từ chối cuối cùng thay vì từ chối đầu tiên.


1
Có vẻ như điều này có thể tổng hợp các lỗi bằng cách biến nó thành một mảng và sử dụng err.push(error), vì vậy tất cả các lỗi có thể bị nổi lên.
ps2goat

4

Tôi đã có cùng một vấn đề và đã giải quyết nó theo cách sau:

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(......)

Trong trường hợp đó, Promise.allsẽ chờ mọi lời hứa sẽ đến resolvedhoặc rejectedtrạng thái.

Và có giải pháp này, chúng tôi đang "dừng catchthực thi" theo cách không chặn. Trên thực tế, chúng tôi không dừng lại bất cứ điều gì, chúng tôi chỉ quay trở lại Promisetrạng thái chờ xử lý sẽ trả lại trạng thái khác Promisekhi giải quyết sau khi hết thời gian.


Nhưng điều đó gọi tất cả các lời hứa theo ý muốn khi bạn chạy Promise.all. Tôi đang tìm cách lắng nghe khi tất cả các lời hứa đã được thực hiện, nhưng bản thân tôi không thực hiện được. Cảm ơn.
SudoPlz

@SudoPlz phương thức all()thực hiện điều đó, nó chờ đợi để thực hiện tất cả các Lời hứa hoặc từ chối ít nhất một trong số đó.
user1016265

Điều đó đúng, nhưng nó không chỉ chờ đợi, nó thực sự gọi / bắt đầu / kích hoạt quá trình. Nếu bạn muốn thực hiện những lời hứa ở một nơi nào khác mà không thể thực hiện được, thì hãy .allkích hoạt mọi thứ.
SudoPlz

@SudoPlz hy vọng điều này sẽ thay đổi ý kiến ​​của bạn jsfiddle.net/d1z1vey5
user1016265

3
Tôi đứng sửa. Cho đến bây giờ tôi nghĩ Promise chỉ chạy khi ai đó gọi chúng (hay còn gọi là thenmột .allcuộc gọi) nhưng chúng chạy khi được tạo.
SudoPlz

2

Điều này phải phù hợp với cách Q thực hiện :

if(!Promise.allSettled) {
    Promise.allSettled = function (promises) {
        return Promise.all(promises.map(p => Promise.resolve(p).then(v => ({
            state: 'fulfilled',
            value: v,
        }), r => ({
            state: 'rejected',
            reason: r,
        }))));
    };
}

2

Tất nhiên câu trả lời của Benjamin Gruenbaum là tuyệt vời ,. Nhưng tôi cũng có thể thấy quan điểm của Nathan Hagen với mức độ trừu tượng có vẻ mơ hồ. Có các thuộc tính đối tượng ngắn như e & vkhông giúp được gì, nhưng tất nhiên điều đó có thể thay đổi.

Trong Javascript có đối tượng Lỗi tiêu chuẩn, được gọi là Error. Lý tưởng nhất là bạn luôn ném một ví dụ / hậu duệ của việc này. Ưu điểm là bạn có thể làm được instanceof Error, và bạn biết có gì đó không ổn.

Vì vậy, sử dụng ý tưởng này, đây là vấn đề của tôi.

Về cơ bản bắt lỗi, nếu lỗi không thuộc loại Lỗi, hãy bọc lỗi bên trong một đối tượng Lỗi. Mảng kết quả sẽ có các giá trị được giải quyết hoặc các đối tượng Lỗi mà bạn có thể kiểm tra.

Ví dụ bên trong sản phẩm khai thác, trong trường hợp bạn sử dụng một số thư viện bên ngoài có thể đã làm reject("error"), thay vì reject(new Error("error")).

Tất nhiên bạn có thể có những lời hứa là bạn đã giải quyết một lỗi, nhưng trong trường hợp đó rất có thể sẽ có ý nghĩa để coi đó là một lỗi dù sao, như ví dụ cuối cùng cho thấy.

Một lợi thế khác của việc này, việc hủy mảng được giữ đơn giản.

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

Thay vì

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

Bạn có thể lập luận rằng việc !error1kiểm tra đơn giản hơn một ví dụ, nhưng bạn cũng phải hủy cả hai 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

Thay vì từ chối, giải quyết nó với một đối tượng. Bạn có thể làm một cái gì đó như thế này khi bạn đang thực hiện lời hứa

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
Đây có vẻ là một công việc tốt đẹp xung quanh, không thanh lịch nhưng sẽ hoạt động
Sunny Tambi

1

Tôi nghĩ rằng các tiêu chuẩn sau một cách tiếp cận hơi khác nhau ... so sánh fn_fast_fail()với fn_slow_fail()... mặc dù sau này không thất bại như vậy ... bạn có thể kiểm tra nếu một hoặc cả hai ablà một thể hiện của Errorthrowrằng Errornếu bạn muốn nó tầm các catchkhối (ví dụ if (b instanceof Error) { throw b; }). Xem 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

Đây là tùy chỉnh của tôi 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;
  });
};

So với Promise.all

  • Nếu tất cả các lời hứa được giải quyết, nó sẽ thực hiện chính xác như tiêu chuẩn.

  • Nếu một trong những lời hứa bị từ chối, nó sẽ trả về lời hứa đầu tiên bị từ chối giống như lời hứa tiêu chuẩn nhưng không giống như nó chờ đợi tất cả các lời hứa sẽ giải quyết / từ chối.

Đối với những người dũng cảm, chúng ta có thể thay đổi Promise.all():

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

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

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

CẨN THẬN . Nói chung, chúng tôi không bao giờ thay đổi các phần dựng sẵn, vì nó có thể phá vỡ các thư viện JS không liên quan khác hoặc xung đột với các thay đổi trong tương lai đối với các tiêu chuẩn JS.

My settledPromisealltương thích ngược với Promise.allvà mở rộng chức năng của nó.

Những người đang phát triển các tiêu chuẩn - tại sao không đưa điều này vào một tiêu chuẩn Hứa mới?


0

Promise.allvới việc sử dụng hiện đại async/awaittiếp cận

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

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

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

-1

Tôi sẽ làm:

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

Bạn có thể thực hiện logic của mình một cách tuần tự thông qua nsynjs thực thi đồng bộ . Nó sẽ tạm dừng trên mỗi lời hứa, chờ giải quyết / từ chối và gán kết quả của độ phân giải cho thuộc datatính hoặc ném ngoại lệ (để xử lý rằng bạn sẽ cần khối thử / bắt). Đây là một ví dụ:

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

Tôi đã sử dụng các mã sau kể từ 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);
                }
            });
        });
    });
};

Chữ ký sử dụng là như thế Promise.all. Sự khác biệt chính là Promise.waitsẽ chờ đợi tất cả những lời hứa sẽ hoàn thành công việc của họ.


-1

Tôi biết rằng câu hỏi này có rất nhiều câu trả lời và tôi chắc chắn phải (nếu không phải tất cả) là chính xác. Tuy nhiên, thật khó cho tôi để hiểu logic / dòng chảy của những câu trả lời này.

Vì vậy, tôi đã xem Triển khai ban đầu Promise.all()và tôi đã cố gắng bắt chước logic đó - ngoại trừ việc không dừng thực thi nếu một Lời hứa thất bại.

  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;
  }

Giải thích:
- Lặp lại đầu vào promisesListvà thực hiện từng Lời hứa.
- Không có vấn đề nếu Lời hứa được giải quyết hoặc bị từ chối: lưu kết quả của Lời hứa trong một resultmảng theo index. Lưu cả trạng thái giải quyết / từ chối ( isSuccess).
- Khi tất cả các Lời hứa đã hoàn thành, hãy trả lại một Lời hứa với kết quả của tất cả các Lời hứa khác.

Ví dụ sử dụng:

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
Đừng cố gắng Promise.alltự thực hiện lại , có quá nhiều thứ sẽ sai. Phiên bản của bạn không xử lý các đầu vào trống chẳng hạn.
Bergi

-4

Tôi không biết thư viện hứa hẹn nào bạn đang sử dụng, nhưng hầu hết đều có thứ gì đó giống như tất cả .

Chỉnh sửa: Ok vì bạn muốn sử dụng ES6 đơn giản mà không có thư viện bên ngoài, không có phương pháp như vậy.

Nói cách khác: Bạn phải lặp lại các lời hứa của mình một cách thủ công và giải quyết một lời hứa kết hợp mới ngay khi tất cả các lời hứa được giải quyết.


Tôi đã chỉnh sửa câu hỏi của mình để làm rõ - Vì ES6 đi kèm với những lời hứa, tôi muốn tránh sử dụng một thư viện khác cho những gì tôi nghĩ là chức năng cơ bản. Tôi đoán một nơi tốt để có được câu trả lời sẽ là sao chép nguồn từ một trong những thư viện hứa hẹn.
Nathan Hagen
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.