Tunggu sampai semua janji selesai meskipun beberapa ditolak

405

Katakanlah saya memiliki satu set Promises yang membuat permintaan jaringan, yang salah satunya akan gagal:

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

Katakanlah saya ingin menunggu sampai semua ini selesai, terlepas dari apakah ada yang gagal. Mungkin ada kesalahan jaringan untuk sumber daya yang saya dapat hidup tanpanya, tetapi jika saya bisa mendapatkannya, saya ingin sebelum melanjutkan. Saya ingin menangani kegagalan jaringan dengan anggun.

Karena Promises.alltidak meninggalkan ruang untuk ini, apa pola yang direkomendasikan untuk menangani ini, tanpa menggunakan perpustakaan janji?

Nathan Hagen
sumber
Apa yang harus dikembalikan dalam array yang dihasilkan untuk janji yang ditolak?
Kuba Wyrostek
9
ES6 menjanjikan dukungan tidak ada metode seperti itu (dan saat ini tampaknya lebih lambat dari Bluebird ). Selain itu, belum semua browser atau mesin mendukungnya. Saya akan sangat menyarankan menggunakan Bluebird, yang datang dengan allSettledyang memenuhi kebutuhan Anda tanpa Anda harus roll sendiri.
Dan Pantry
@ KubaWyrostek Saya pikir Anda mengemukakan alasan Promise.all tidak memiliki perilaku ini, yang menurut saya masuk akal. Ini bukan cara kerjanya, tetapi pandangan alternatif akan mengatakan Janji. Kita harus mengembalikan janji khusus yang tidak pernah gagal - dan Anda akan mendapatkan kesalahan yang dilemparkan sebagai argumen yang mewakili janji gagal.
Nathan Hagen
Untuk menambah apa yang Dan bagikan, fungsi allSettled / settleAll like yang dimiliki bluebird dapat digunakan melalui fungsi "reflect".
user3344977
2
@Coli: Hmm, kurasa tidak. Promise.allakan menolak segera setelah ada satu janji yang menolak, jadi idiom yang Anda usulkan tidak menjamin bahwa semua janji telah terpenuhi.
Jörg W Mittag

Jawaban:

310

Perbarui, Anda mungkin ingin menggunakan bawaan bawaan Promise.allSettled:

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

Sebagai fakta yang menyenangkan, jawaban di bawah ini merupakan penemuan sebelumnya dalam menambahkan metode itu ke bahasa:]


Tentu, Anda hanya perlu reflect:

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

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

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

Atau dalam contoh Anda:

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");
});
Benjamin Gruenbaum
sumber
3
Saya pikir ini adalah solusi yang bagus. Bisakah Anda mengubahnya untuk memasukkan sintaksis yang lebih sederhana? Inti masalahnya adalah bahwa jika Anda ingin menangani kesalahan dalam sub-janji, Anda harus menangkapnya dan mengembalikan kesalahan tersebut. Jadi misalnya: gist.github.com/nhagen/a1d36b39977822c224b8
Nathan Hagen
3
@NathanHagen memungkinkan Anda mengetahui apa yang ditolak dan apa yang dipenuhi dan mengekstrak masalahnya ke operator yang dapat digunakan kembali.
Benjamin Gruenbaum
4
Menanggapi masalah saya sendiri, saya telah membuat paket npm berikut: github.com/Bucabug/promise-reflect npmjs.com/package/promise-reflect
SamF
2
Saya mengalami masalah ini beberapa waktu yang lalu dan saya membuat paket npm ini untuknya: npmjs.com/package/promise-all-soft-fail
velocity_distance
5
Apakah kata tersebut reflectmerupakan kata yang umum dalam ilmu komputer? Bisakah Anda tautkan ke tempat ini dijelaskan seperti di wikipedia atau sesuatu. Saya mencari dengan susah payah Promise.all not even first rejecttetapi tidak tahu untuk mencari "Renungkan". Haruskah ES6 memiliki Promise.reflectyang seperti "Janji. Semua tapi benar-benar semua"?
Noitidart
253

Jawaban serupa, tetapi lebih idiomatis untuk ES6 mungkin:

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>

Bergantung pada jenis nilai yang dikembalikan, kesalahan sering kali dapat dibedakan dengan cukup mudah (mis. Penggunaan undefineduntuk "tidak peduli", typeofuntuk nilai bukan objek biasa result.message, result.toString().startsWith("Error:")dll.)

jib
sumber
1
@ KarlBateman, saya pikir Anda bingung. Fungsi pesanan diselesaikan atau ditolak tidak penting di sini karena .map(p => p.catch(e => e))bagian mengubah semua penolakan menjadi nilai yang diselesaikan, jadi Promise.allmasih menunggu semuanya selesai apakah fungsi individu menyelesaikan atau menolak, terlepas dari berapa lama waktu yang diperlukan. Cobalah.
jib
39
.catch(e => console.log(e));tidak pernah dipanggil karena ini tidak pernah gagal
fregante
4
@ bfred.it Itu benar. Meskipun mengakhiri rantai janji dengan catchumumnya adalah praktik yang baik IMHO .
jib
2
@SuhailGupta Ia menangkap kesalahan edan mengembalikannya sebagai nilai reguler (sukses). Sama seperti p.catch(function(e) { return e; })hanya lebih pendek. returntersirat.
jib
1
@JustinReusnow sudah meliput komentar. Selalu praktik yang baik untuk menghentikan rantai jika Anda menambahkan kode nanti.
jib
71

Jawaban Benjamin menawarkan abstraksi yang bagus untuk menyelesaikan masalah ini, tetapi saya berharap untuk solusi yang kurang abstrak. Cara eksplisit untuk mengatasi masalah ini adalah dengan hanya memanggil .catchjanji internal, dan mengembalikan kesalahan dari panggilan balik mereka.

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

Mengambil langkah ini lebih jauh, Anda dapat menulis penangan tangkapan generik yang terlihat seperti ini:

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

maka kamu bisa melakukannya

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

Masalah dengan ini adalah bahwa nilai-nilai yang ditangkap akan memiliki antarmuka yang berbeda dari nilai-nilai yang tidak tertangkap, jadi untuk membersihkan ini Anda mungkin melakukan sesuatu seperti:

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

Jadi sekarang Anda bisa melakukan ini:

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

Kemudian untuk tetap KERING, Anda mendapatkan jawaban Benjamin:

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

di mana sekarang terlihat seperti

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

Manfaat dari solusi kedua adalah abstrak dan KERING. Kelemahannya adalah Anda memiliki lebih banyak kode, dan Anda harus ingat untuk mencerminkan semua janji Anda untuk membuat semuanya konsisten.

Saya akan mencirikan solusi saya sebagai eksplisit dan KISS, tetapi memang kurang kuat. Antarmuka tidak menjamin bahwa Anda tahu persis apakah janji itu berhasil atau gagal.

Misalnya, Anda mungkin memiliki ini:

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

Ini tidak akan ketahuan a.catch, jadi

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

Tidak ada cara untuk mengetahui mana yang fatal dan mana yang tidak. Jika itu penting maka Anda akan ingin menegakkan dan antarmuka yang melacak apakah itu berhasil atau tidak (yang reflecttidak).

Jika Anda hanya ingin menangani kesalahan dengan anggun, maka Anda bisa memperlakukan kesalahan sebagai nilai yang tidak ditentukan:

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

Dalam kasus saya, saya tidak perlu tahu kesalahannya atau bagaimana kegagalannya - saya hanya peduli apakah saya memiliki nilai atau tidak. Saya akan membiarkan fungsi yang menghasilkan janji khawatir tentang pendataan kesalahan spesifik.

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

Dengan begitu, sisa aplikasi dapat mengabaikan kesalahannya jika diinginkan, dan memperlakukannya sebagai nilai yang tidak ditentukan jika diinginkan.

Saya ingin fungsi tingkat tinggi saya gagal dengan aman dan tidak khawatir tentang detail mengapa dependensinya gagal, dan saya juga lebih suka KISS daripada KERING ketika saya harus melakukan tradeoff - yang pada akhirnya mengapa saya memilih untuk tidak menggunakan reflect.

Nathan Hagen
sumber
1
@Benjamin Saya pikir solusi @ Nathan sangat mudah dan idiomatis untuk Promises. Sementara reflectkode meningkatkan Anda digunakan kembali, itu juga menetapkan tingkat abstraksi lain. Karena jawaban Nathan sejauh ini hanya menerima sebagian kecil upvotes dibandingkan dengan jawaban Anda, saya bertanya-tanya apakah ini merupakan indikasi masalah dengan solusinya, yang belum saya kenali.
2
@ LUH3417 solusi ini secara konseptual kurang suara karena memperlakukan kesalahan sebagai nilai dan tidak memisahkan kesalahan dari non-kesalahan. Sebagai contoh jika salah satu dari janji-janji itu memutuskan secara sah suatu nilai yang dapat dilemparkan (yang sepenuhnya mungkin), ini akan sangat buruk.
Benjamin Gruenbaum
2
@BenjaminGruenbaum Jadi misalnya, new Promise((res, rej) => res(new Error('Legitimate error'))tidakkah bisa dibedakan new Promise(((res, rej) => rej(new Error('Illegitimate error'))? Atau lebih lanjut, Anda tidak akan dapat memfilter x.status? Saya akan menambahkan poin ini ke jawaban saya sehingga perbedaannya lebih jelas
Nathan Hagen
3
Alasan ini adalah ide yang buruk adalah karena ia mengikat implementasi Janji untuk kasus penggunaan tertentu yang hanya pernah digunakan dalam Promise.all()varian tertentu , itu juga kemudian menjadi kewajiban konsumen Promise untuk mengetahui bahwa janji tertentu tidak akan menolak tetapi akan menelan kesalahan itu. Bahkan reflect()metode ini dapat dibuat kurang 'abstrak' dan lebih eksplisit dengan menyebutnya PromiseEvery(promises).then(...). Kompleksitas jawaban di atas dibandingkan dengan Benjamin harus mengatakan banyak tentang solusi ini.
Neil
33

Ada proposal selesai untuk fungsi yang dapat menyelesaikan ini secara asli, dalam vanilla Javascript Promise.allSettled:, yang telah membuatnya ke tahap 4, resmi di ES2020, dan diimplementasikan di semua lingkungan modern . Sangat mirip dengan reflectfungsi dalam jawaban lain ini . Ini sebuah contoh, dari halaman proposal. Sebelumnya, Anda harus melakukan:

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

Menggunakan Promise.allSettledsebaliknya, di atas akan setara dengan:

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

Mereka yang menggunakan lingkungan modern akan dapat menggunakan metode ini tanpa perpustakaan . Di dalamnya, cuplikan berikut ini harus dijalankan tanpa masalah:

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

Keluaran:

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

Untuk browser yang lebih lama, ada polyfill yang memenuhi spesifikasi di sini .

Performa Tertentu
sumber
1
Ini tahap 4 dan seharusnya mendarat di ES2020.
Estus Flask
Juga tersedia di Node 12 :)
Callum M
Sekalipun jawaban yang lain masih valid, jawaban ini harus mendapatkan lebih banyak upvotes karena ini adalah cara terbaru untuk menyelesaikan masalah ini.
Yakub
9

Saya benar-benar menyukai jawaban Benjamin, dan bagaimana dia pada dasarnya mengubah semua janji menjadi yang selalu menyelesaikan, tetapi kadang-kadang dengan kesalahan sebagai akibatnya. :)
Ini usaha saya atas permintaan Anda jika Anda mencari alternatif. Metode ini hanya memperlakukan kesalahan sebagai hasil yang valid, dan dikodekan mirip dengan yang Promise.alllain:

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);
  });
}
Kuba Wyrostek
sumber
Ini biasanya disebut settle. Kami memiliki itu juga di bluebird, saya suka mencerminkan lebih baik tetapi ini adalah solusi yang layak untuk ketika Anda memiliki ini untuk sebuah array.
Benjamin Gruenbaum
2
OK, puas akan menjadi nama yang lebih baik. :)
Kuba Wyrostek
Ini sangat mirip dengan antipattern konstruksi janji yang eksplisit. Perlu dicatat bahwa Anda tidak pernah harus menulis fungsi seperti itu sendiri, tetapi gunakan salah satu persediaan perpustakaan Anda (OK, ES6 asli agak sedikit).
Bergi
Bisakah Anda menggunakan Promisekonstruktor dengan benar (dan menghindari hal itu var resolve)?
Bergi
Bergi, jangan ragu untuk mengubah jawaban yang menurut Anda perlu.
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;
    }
});

The Promise.allakan menelan janji ditolak dan menyimpan kesalahan dalam variabel, sehingga akan kembali ketika semua janji-janji telah diselesaikan. Kemudian Anda dapat membuang kesalahan keluar, atau melakukan apa pun. Dengan cara ini, saya kira Anda akan keluar dari penolakan terakhir, bukan yang pertama.

martin770
sumber
1
Sepertinya ini bisa mengagregasi kesalahan dengan membuatnya menjadi array dan menggunakan err.push(error), jadi semua kesalahan bisa digelembungkan.
ps2goat
4

Saya memiliki masalah yang sama dan telah menyelesaikannya dengan cara berikut:

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

Dalam hal ini Promise.allakan menunggu setiap Janji akan datang resolvedatau rejectedmenyatakan.

Dan memiliki solusi ini kami "menghentikan catcheksekusi" dengan cara yang tidak menghalangi. Faktanya, kami tidak menghentikan apa pun, kami hanya mengembalikan kembali Promisedalam keadaan tertunda yang mengembalikan yang lain Promiseketika diselesaikan setelah batas waktu.

pengguna1016265
sumber
Tapi itu meminta semua janji sesuka hati saat Anda berlari Promise.all. Saya mencari cara untuk mendengarkan ketika semua janji telah diajukan, tetapi tidak meminta mereka sendiri. Terima kasih.
SudoPlz
@SudoPlz metode all()melakukan itu, ia menunggu pemenuhan semua Janji atau penolakan setidaknya satu dari itu.
user1016265
itu benar, tetapi itu tidak hanya menunggu, itu benar-benar memanggil / memulai / memulai proses. Jika Anda ingin menjalankan janji-janji di tempat lain yang tidak mungkin, karena .allsemuanya terbakar.
SudoPlz
@SudoPlz harap ini akan mengubah opini Anda jsfiddle.net/d1z1vey5
user1016265
3
Saya berdiri dikoreksi. Sampai sekarang saya pikir Janji hanya berjalan ketika seseorang memanggil mereka (alias panggilan thenatau .all) tetapi mereka berjalan ketika dibuat.
SudoPlz
2

Ini harus konsisten dengan bagaimana Q melakukannya :

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,
        }))));
    };
}
Mpen
sumber
2

Jawaban Benjamin Gruenbaum tentu saja hebat. Tapi saya juga bisa melihat apakah sudut pandang Nathan Hagen dengan tingkat abstraksi tampak tidak jelas. Memiliki properti objek pendek sukae & v juga tidak membantu, tapi tentu saja itu bisa diubah.

Di Javascript ada objek Kesalahan standar, yang disebut Error,. Idealnya Anda selalu melempar turunan / keturunan ini. Keuntungannya adalah Anda bisa melakukannya instanceof Error, dan Anda tahu ada sesuatu yang salah.

Jadi menggunakan ide ini, inilah pendapat saya tentang masalahnya.

Pada dasarnya menangkap kesalahan, jika kesalahan bukan dari jenis Kesalahan, bungkus kesalahan di dalam objek Kesalahan. Array yang dihasilkan akan memiliki nilai yang diselesaikan, atau objek Kesalahan yang dapat Anda periksa.

Contoh di dalam tangkapan, jika Anda menggunakan beberapa perpustakaan eksternal yang mungkin melakukannya reject("error"), bukan reject(new Error("error")).

Tentu saja Anda dapat memiliki janji-janji Anda menyelesaikan kesalahan, tetapi dalam kasus itu kemungkinan besar masuk akal untuk memperlakukan sebagai kesalahan, seperti contoh terakhir menunjukkan.

Keuntungan lain melakukannya, penghancuran array dibuat sederhana.

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

Dari pada

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

Anda bisa berargumen bahwa !error1cek itu lebih sederhana daripada contoh, tetapi Anda juga harus merusak keduanya 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();

Keith
sumber
2

Alih-alih menolak, atasi dengan objek. Anda bisa melakukan sesuatu seperti ini ketika Anda menerapkan janji

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

NuOne
sumber
1
Ini terlihat bagus, tidak elegan tetapi akan berhasil
Sunny Tambi
1

Saya pikir tawaran mengikuti pendekatan yang sedikit berbeda ... dibandingkan fn_fast_fail()dengan fn_slow_fail()... meskipun yang terakhir tidak gagal seperti ... Anda dapat memeriksa apakah salah satu atau kedua adan bmerupakan contoh dari Errordan throwbahwa Errorjika Anda ingin jangkauan yang catchblok (misalnya if (b instanceof Error) { throw b; }). Lihat 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
drmrbrewer
sumber
0

Ini kebiasaan saya 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;
  });
};

Dibandingkan dengan Promise.all

  • Jika semua janji terselesaikan, itu berfungsi persis seperti yang standar.

  • Jika satu dari lebih janji ditolak, itu mengembalikan yang pertama ditolak sama dengan yang standar tapi tidak seperti itu menunggu semua janji untuk diselesaikan / ditolak.

Untuk yang berani kita bisa berubah Promise.all():

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

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

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

HATI-HATI . Secara umum kami tidak pernah mengubah built-in, karena mungkin merusak perpustakaan JS lain yang tidak terkait atau berbenturan dengan perubahan di masa depan dengan standar JS.

My settledPromiseallbackward compatible denganPromise.all dan memperluas fungsinya.

Orang yang mengembangkan standar - mengapa tidak memasukkan ini ke standar Janji baru?

Edward
sumber
0

Promise.alldengan menggunakan async/awaitpendekatan modern

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

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

const dataFromPromise1 = data[0]
const dataFromPromise2 = data[1]
Maksim Shamihulau
sumber
-1

Saya akan lakukan:

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
FRocha
sumber
-1

Anda dapat menjalankan logika Anda secara berurutan melalui nsynjs executor yang sinkron . Ini akan berhenti pada setiap janji, menunggu resolusi / penolakan, dan baik menetapkan hasil tekad ke dataproperti, atau melemparkan pengecualian (untuk menangani bahwa Anda akan perlu mencoba / menangkap blokir). Berikut ini sebuah contoh:

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>

amaksr
sumber
-1

Saya telah menggunakan kode berikut sejak 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);
                }
            });
        });
    });
};

Tanda tangan penggunaannya seperti Promise.all. Perbedaan utama adalah bahwa Promise.waitakan menunggu semua janji untuk menyelesaikan pekerjaan mereka.

pengguna2273990
sumber
-1

Saya tahu bahwa pertanyaan ini memiliki banyak jawaban, dan saya yakin harus (jika tidak semua) benar. Namun sangat sulit bagi saya untuk memahami logika / aliran jawaban ini.

Jadi saya melihat Implementasi Asli pada Promise.all(), dan saya mencoba untuk meniru logika itu - dengan pengecualian tidak menghentikan eksekusi jika satu Janji gagal.

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

Penjelasan:
- Ulangi input promisesListdan jalankan setiap Janji.
- Tidak masalah jika Janji terselesaikan atau ditolak: simpan hasil Janji dalam resultarray sesuai denganindex . Simpan juga status tekad / tolak ( isSuccess).
- Setelah semua Janji selesai, kembalikan satu Janji dengan hasil dari semua yang lain.

Contoh penggunaan:

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
*/
Gil Epshtain
sumber
2
Jangan mencoba menerapkan kembali Promise.alldiri Anda, ada terlalu banyak hal yang salah. Versi Anda tidak menangani input kosong misalnya.
Bergi
-4

Saya tidak tahu perpustakaan janji mana yang Anda gunakan, tetapi sebagian besar memiliki sesuatu seperti allSettled .

Sunting: Oke karena Anda ingin menggunakan ES6 biasa tanpa pustaka eksternal, tidak ada metode seperti itu.

Dengan kata lain: Anda harus mengulangi janji Anda secara manual dan menyelesaikan janji gabungan baru segera setelah semua janji diselesaikan.

Sebastian S
sumber
Saya telah mengedit pertanyaan saya untuk menjelaskan - Karena ES6 datang dengan janji-janji, saya ingin menghindari menggunakan perpustakaan lain karena apa yang saya pikir adalah fungsionalitas dasar. Saya kira tempat yang baik untuk mendapatkan jawabannya adalah dengan menyalin sumber dari salah satu perpustakaan yang dijanjikan.
Nathan Hagen