Poczekaj, aż wszystkie obietnice się spełnią, nawet jeśli niektóre zostaną odrzucone


405

Powiedzmy, że mam zestaw Promises, które wysyłają żądania sieciowe, z których jeden się nie powiedzie:

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

Powiedzmy, że chcę poczekać, aż wszystkie się zakończą, niezależnie od tego, czy coś się nie powiedzie. Może występować błąd sieci dla zasobu, bez którego mogę żyć, ale jeśli mogę go zdobyć, chcę, zanim przejdę dalej. Chcę z wdziękiem radzić sobie z awariami sieci.

Ponieważ Promises.allnie pozostawia na to miejsca, jaki jest zalecany sposób postępowania bez korzystania z biblioteki obietnic?


Co należy zwrócić w wynikowej tablicy dla obietnic, które zostały odrzucone?
Kuba Wyrostek

9
ES6 obiecuje, że nie obsługuje takiej metody (i obecnie są najwyraźniej wolniejsze niż Bluebird ). Ponadto nie wszystkie przeglądarki lub silniki obsługują je jeszcze. Chciałbym mocno zalecamy korzystanie Bluebird, która pochodzi z allSettledktóra zaspokaja swoje potrzeby bez konieczności toczyć własną rękę.
Dan Pantry

@KubaWyrostek Myślę, że przywołujesz powód Promise.all nie ma takiego zachowania, które moim zdaniem ma sens. Nie tak to działa, ale alternatywnym poglądem byłoby powiedzenie Promise. Wszystkie powinny zwrócić specjalną obietnicę, która nigdy nie zawodzi - i dostaniesz błąd, który został zgłoszony jako argument reprezentujący nieudaną obietnicę.
Nathan Hagen

Aby dodać do tego, co Dan udostępnił, funkcjonalność allSettled / settleAll like, którą ma bluebird, można wykorzystać za pomocą funkcji „reflect”.
user3344977

2
@Coli: Hmm, nie sądzę. Promise.allodrzuci, gdy tylko jakakolwiek obietnica odrzuci, więc twój proponowany idiom nie gwarantuje, że wszystkie obietnice zostaną spełnione.
Jörg W Mittag

Odpowiedzi:


309

Aktualizacja, prawdopodobnie chcesz użyć wbudowanego natywnego Promise.allSettled:

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

Zabawnym faktem jest to, że poniższa odpowiedź stanowiła wcześniejszy sposób dodawania tej metody do języka:]


Jasne, potrzebujesz tylko reflect:

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

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

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

Lub w twoim przykładzie:

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
Myślę, że to świetne rozwiązanie. Czy możesz to zmienić, dodając prostszą składnię? Sedno problemu polega na tym, że jeśli chcesz obsługiwać błędy w sub-obietnicach, powinieneś je złapać i zwrócić błąd. Na przykład: gist.github.com/nhagen/a1d36b39977822c224b8
Nathan Hagen

3
@NathanHagen pozwala dowiedzieć się, co zostało odrzucone, a co spełnione, i przekazuje problem operatorowi wielokrotnego użytku.
Benjamin Gruenbaum,

4
W odpowiedzi na mój problem utworzyłem następujący pakiet npm: github.com/Bucabug/promise-reflect npmjs.com/package/promise-reflect
SamF


5
Czy to słowo reflectjest powszechne w informatyce? Czy możesz podać link do miejsca, w którym zostało to wyjaśnione, np. Na wikipedii lub coś w tym stylu. Ciężko szukałem, Promise.all not even first rejectale nie wiedziałem, aby wyszukać „Reflect”. Czy ES6 powinien mieć coś w stylu Promise.reflect„Promise.all, ale naprawdę wszystko”?
Noitidart

253

Podobna odpowiedź, ale może bardziej idiomatyczna dla 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>

W zależności od rodzaju (y) wartości zwracane błędy można często odróżnić dość łatwo (np zastosowanie undefineddo „nie obchodzi”, typeofdla prostych wartości non-object, result.message, result.toString().startsWith("Error:")itd.)


1
@KarlBateman Myślę, że jesteś zdezorientowany. Funkcje porządkowania są rozpatrywane lub odrzucane w tym przypadku, nie ma to znaczenia, ponieważ .map(p => p.catch(e => e))część zamienia wszystkie odrzucenia na wartości rozstrzygnięte, więc Promise.allnadal czeka, aż wszystko się skończy, czy poszczególne funkcje zostaną rozpatrzone lub odrzucone, niezależnie od tego, jak długo to potrwa. Spróbuj.
wysięgnik

39
.catch(e => console.log(e));nigdy nie jest nazywany, ponieważ to nigdy nie zawodzi
fregante

4
@ bfred.it To prawda. Chociaż kończenie łańcuchów obietnic catchjest ogólnie dobrą praktyką IMHO .
wysięg

2
@ SuhailGupta Łapie błąd ei zwraca go jako zwykłą (sukces) wartość. Taki sam jak p.catch(function(e) { return e; })tylko krótszy. returnjest niejawny.
wysięg

1
@JustinReusnow jest już opisany w komentarzach. Zawsze dobrą praktyką jest kończenie łańcuchów w przypadku późniejszego dodania kodu.
wysięgnik

71

Odpowiedź Benjamina oferuje świetną abstrakcję do rozwiązania tego problemu, ale liczyłem na mniej abstrakcyjne rozwiązanie. Wyraźnym sposobem rozwiązania tego problemu jest po prostu wywołanie .catchwewnętrznych obietnic i zwrócenie błędu z ich wywołania zwrotnego.

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

Idąc o krok dalej, możesz napisać ogólną procedurę obsługi połowów, która wygląda następująco:

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

to możesz zrobić

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

Problem polega na tym, że przechwycone wartości będą miały inny interfejs niż nieprzechwycone wartości, więc aby to wyczyścić, możesz zrobić coś takiego:

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

Teraz możesz to zrobić:

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

Następnie, aby zachować SUCHO, dochodzisz do odpowiedzi Benjamina:

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

gdzie teraz wygląda

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

Zaletą drugiego rozwiązania jest to, że jest ono abstrakcyjne i SUCHE. Minusem jest to, że masz więcej kodu i musisz pamiętać, aby odzwierciedlić wszystkie swoje obietnice, aby wszystko było spójne.

Scharakteryzowałbym moje rozwiązanie jako jednoznaczne i KISS, ale rzeczywiście mniej niezawodne. Interfejs nie gwarantuje, że dokładnie wiesz, czy obietnica się powiodła, czy nie.

Na przykład możesz mieć to:

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

To nie zostanie złapane a.catch, więc

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

Nie ma sposobu, aby stwierdzić, który był śmiertelny, a który nie. Jeśli to ważne, będziesz chciał wymusić i interfejs, który śledzi, czy się udało, czy nie (coreflect robi).

Jeśli chcesz po prostu z wdziękiem obsługiwać błędy, możesz traktować je jako niezdefiniowane wartości:

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

W moim przypadku nie muszę znać błędu ani tego, jak się nie powiódł - dbam tylko o to, czy mam wartość, czy nie. Pozwolę, aby funkcja generująca obietnicę martwiła się rejestrowaniem konkretnego błędu.

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

W ten sposób reszta aplikacji może zignorować swój błąd, jeśli chce, i traktować go jako niezdefiniowaną wartość, jeśli chce.

Chcę, aby moje funkcje na wysokim poziomie zawiodły bezpiecznie i nie martwiłem się szczegółami, dlaczego zawiodły jego zależności, a także wolę KISS niż OSUSZANIE, gdy muszę dokonać tego kompromisu - i ostatecznie zdecydowałem się nie używać reflect.


1
@Benjamin Myślę, że rozwiązanie @ Nathana jest bardzo proste i idiomatyczne dla Promises. Chociaż reflectpoprawiasz ponowne użycie kodu, ustanawia on również inny poziom abstrakcji. Ponieważ odpowiedź Nathana do tej pory otrzymała jedynie ułamek głosów pozytywnych w porównaniu do twojej, zastanawiam się, czy to wskazuje na problem z jego rozwiązaniem, którego jeszcze nie rozpoznałem.

2
@ LUH3417 to rozwiązanie ma mniej dźwięku pod względem koncepcyjnym, ponieważ traktuje błędy jako wartości i nie oddziela błędów od błędów. Na przykład, jeśli jedna z obietnic prawidłowo spełnia wartość, którą można wyrzucić (co jest całkowicie możliwe), to psuje się całkiem źle.
Benjamin Gruenbaum,

2
@BenjaminGruenbaum Więc na przykład new Promise((res, rej) => res(new Error('Legitimate error'))nie można odróżnić new Promise(((res, rej) => rej(new Error('Illegitimate error'))? Lub dalej, nie będziesz w stanie filtrować według x.status? Dodam ten punkt do mojej odpowiedzi, aby różnica była bardziej wyraźna
Nathan Hagen

3
Powodem, dla którego jest to zły pomysł, jest to, że wiąże on wdrożenie Obietnicy z konkretnym przypadkiem użycia, który jest kiedykolwiek używany tylko w konkretnym Promise.all()wariancie, a następnie konsumentowi Obietnicy spoczywa obowiązek wiedzieć, że konkretna obietnica nie zostanie odrzucona, ale zostanie odrzucona połknij to błędy. W rzeczywistości reflect()metodę tę można by uczynić mniej „abstrakcyjną”, a bardziej wyraźną, nazywając ją PromiseEvery(promises).then(...). Złożoność powyższej odpowiedzi w porównaniu z odpowiedzią Benjamina powinna wiele powiedzieć o tym rozwiązaniu.
Neil

33

Istnieje gotowa propozycja funkcji, która może tego dokonać natywnie, w waniliowym JavaScripcie: Promise.allSettledktóra przeszła do etapu 4, jest oficjalna w ES2020 i jest implementowana we wszystkich nowoczesnych środowiskach . Jest bardzo podobny do reflectfunkcji z tej drugiej odpowiedzi . Oto przykład ze strony propozycji. Wcześniej musiałbyś zrobić:

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

Użycie Promise.allSettledzamiast tego powyższego będzie równoznaczne z:

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

Osoby korzystające z nowoczesnych środowisk będą mogły korzystać z tej metody bez żadnych bibliotek . W nich następujący fragment kodu powinien działać bez problemów:

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

Wynik:

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

W przypadku starszych przeglądarek dostępny jest tutaj wypełnienie zgodne ze specyfikacją .


1
Jest to etap 4 i ma wylądować w ES2020.
Estus Kolba

Dostępne również w węźle 12 :)
Callum M

Nawet jeśli pozostałe odpowiedzi są nadal aktualne, ta powinna uzyskać więcej głosów pozytywnych, ponieważ jest to najbardziej aktualny sposób rozwiązania tego problemu.
Jakub

9

Naprawdę podoba mi się odpowiedź Benjamina i to, jak zasadniczo zamienia wszystkie obietnice w zawsze rozwiązujące, ale czasem z błędem jako rezultatem. :)
Oto moja próba na twoją prośbę na wypadek, gdybyś szukał alternatyw. Ta metoda po prostu traktuje błędy jako prawidłowe wyniki i jest kodowana podobnie do Promise.allinnych:

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

Zazwyczaj jest to nazywane settle. Mamy to również w bluebird, lubię lepiej odzwierciedlać, ale jest to realne rozwiązanie, gdy masz to dla tablicy.
Benjamin Gruenbaum,

2
OK, osiedlenie się będzie rzeczywiście lepszym imieniem. :)
Kuba Wyrostek

Wygląda to podobnie do jawnej obietnicy budowy antipattern. Należy zauważyć, że nigdy nie powinieneś pisać takiej funkcji sam, ale używaj tej, którą dostarcza twoja biblioteka (OK, natywny ES6 jest trochę skromny).
Bergi,

Czy możesz Promisepoprawnie używać konstruktora (i unikać tego var resolve)?
Bergi,

Bergi, możesz zmienić odpowiedź, jeśli uznasz to za konieczne.
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.allPołknie każdą odrzuconą obietnicy i przechowywanie błędu w zmiennej, tak powróci, gdy wszystkie obietnice zostały rozwiązane. Następnie możesz ponownie wyrzucić błąd lub zrobić cokolwiek innego. W ten sposób wydaje mi się, że wyszedłbyś z ostatniego odrzucenia zamiast pierwszego.


1
Wygląda na to, że to może zsumować błędy, tworząc z nich tablicę i używając err.push(error), dzięki czemu wszystkie błędy mogą zostać wyrzucone z bufora.
ps2goat

4

Miałem ten sam problem i rozwiązałem go w następujący sposób:

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

W takim przypadku Promise.allbędzie czekać na wejście resolvedlub rejectedstan każdej Obietnicy .

Dzięki temu rozwiązaniu „zatrzymujemy catchwykonywanie” w sposób nieblokujący. W rzeczywistości niczego nie zatrzymujemy, po prostu wracamy Promisedo stanu oczekującego, który zwraca inny, Promisegdy zostanie rozwiązany po upływie limitu czasu.


Ale to przywołuje wszystkie obietnice do woli, kiedy biegniesz Promise.all. Szukam sposobu, aby słuchać, kiedy wszystkie obietnice zostały przywołane, ale nie przywołuję ich osobiście. Dzięki.
SudoPlz

@SudoPlz metoda all()robi to, czeka na spełnienie wszystkich obietnic lub odrzucenie co najmniej jednej z nich.
user1016265,

to prawda, ale nie tylko czeka, w rzeczywistości wywołuje / uruchamia / uruchamia proces. Jeśli chcesz rozpalić obietnice w innym miejscu, co nie byłoby możliwe, ponieważ .allwszystko rozpala.
SudoPlz

@SudoPlz mam nadzieję, że to zmieni twoją opinię jsfiddle.net/d1z1vey5
user1016265

3
Poprawiono mnie. Do tej pory myślałem, że Obietnice działają tylko wtedy, gdy ktoś je wywołuje (aka thenlub .allpołączenie), ale działają po utworzeniu.
SudoPlz

2

Powinno to być spójne z tym, jak to robi Q :

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

Odpowiedź Benjamina Gruenbauma jest oczywiście świetna. Ale widzę też, że punkt widzenia Nathana Hagena z poziomem abstrakcji wydaje się niejasny. Posiadanie krótkich właściwości obiektu, takich jak e & v, również nie pomaga, ale oczywiście można to zmienić.

W JavaScript nie jest standard obiektu Error o nazwie Error,. Idealnie zawsze rzucasz instancją / potomkiem tego. Zaletą jest to, że możesz to zrobićinstanceof Error i wiesz, że coś jest błędem.

Korzystając z tego pomysłu, oto moje podejście do problemu.

Zasadniczo złap błąd, jeśli błąd nie jest typu Błąd, zawiń błąd w obiekcie Error. Wynikowa tablica będzie miała albo rozstrzygnięte wartości, albo Obiekty błędów, które można sprawdzić.

Instancja wewnątrz haczyka jest na wypadek, gdybyś użył biblioteki zewnętrznej, która może reject("error"), zamiast reject(new Error("error")).

Oczywiście możesz mieć obietnice, jeśli rozwiążesz błąd, ale w takim przypadku najprawdopodobniej i tak byłoby traktowane jako błąd, jak pokazuje ostatni przykład.

Inną zaletą robienia tego jest niszczenie tablic, które jest proste.

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

Zamiast

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

Można argumentować, że !error1sprawdzenie jest prostsze niż instanceof, ale musisz zniszczyć oba 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

Zamiast odrzucać, rozwiąż go za pomocą obiektu. Możesz zrobić coś takiego podczas realizacji obietnicy

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
Wygląda to całkiem fajnie, nie elegancko, ale zadziała
Sunny Tambi,

1

Myślę następujące oferty nieco inne podejście ... porównać fn_fast_fail()z fn_slow_fail()... choć ten ostatni nie zawieść jako takie ... można sprawdzić, czy jeden lub oba ai bjest instancją Errori throwże Errorjeśli chcesz, aby osiągnąć catchblokowe (na przykład if (b instanceof Error) { throw b; }). Zobacz 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

Oto mój zwyczaj 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;
  });
};

W porównaniu do Promise.all

  • Jeśli wszystkie obietnice zostaną rozwiązane, działa dokładnie tak samo jak standardowe.

  • Jeśli jedna lub więcej obietnic zostanie odrzuconych, zwraca pierwszą odrzuconą podobnie jak standardowa, ale w przeciwieństwie do oczekiwania na wszystkie obietnice, które zostaną rozpatrzone / odrzucone.

Dla odważnych możemy zmienić Promise.all():

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

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

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

OSTROŻNY . Zasadniczo nigdy nie zmieniamy wbudowanych, ponieważ może to zepsuć inne niepowiązane biblioteki JS lub kolidować z przyszłymi zmianami standardów JS.

Mój settledPromisealljest wstecznie kompatybilny zPromise.all i rozszerza swoją funkcjonalność.

Ludzie, którzy opracowują standardy - dlaczego nie dołączyć do nowego standardu Promise?


0

Promise.allz zastosowaniem nowoczesnego async/awaitpodejścia

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

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

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

-1

Chciałbym zrobić:

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

Możesz wykonywać swoją logikę sekwencyjnie za pomocą synchronicznego executora nsynjs . Zatrzymuje się przy każdej obietnicy, czeka na rozstrzygnięcie / odrzucenie i przypisuje wynik rozstrzygania do datawłaściwości lub zgłasza wyjątek (do obsługi, że będziesz potrzebował zablokować try / catch). Oto przykład:

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

Używam następujących kodów od 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);
                }
            });
        });
    });
};

Podpis użytkowania jest taki sam Promise.all. Główną różnicą jest to, że Promise.waitbędą czekać na wszystkie obietnice zakończenia pracy.


-1

Wiem, że to pytanie ma wiele odpowiedzi i jestem pewien, że muszą (jeśli nie wszystkie) być poprawne. Jednak bardzo trudno było mi zrozumieć logikę / przepływ tych odpowiedzi.

Spojrzałem więc na Pierwotną Implementację Promise.all()i próbowałem naśladować tę logikę - z wyjątkiem tego, że nie zatrzymam wykonania, jeśli jedna Obietnica się nie powiedzie.

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

Objaśnienie:
- Zapętlić dane wejściowe promisesListi wykonać każdą obietnicę.
- Bez względu na to, czy obietnica została rozwiązana, czy odrzucona: zapisz wynik obietnicy w resulttablicy zgodnie z index. Zapisz także status rozwiązania / odrzucenia ( isSuccess).
- Po zakończeniu wszystkich obietnic, zwróć jedną obietnicę z wynikiem wszystkich pozostałych.

Przykład zastosowania:

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
Nie próbuj się ponownie wdrażać Promise.all, zbyt wiele rzeczy pójdzie nie tak. Twoja wersja nie obsługuje na przykład pustych danych wejściowych.
Bergi,

-4

Nie wiem, jakiej biblioteki obietnic używasz, ale większość ma coś takiego jak allSettled .

Edycja: Ok, ponieważ chcesz używać zwykłego ES6 bez bibliotek zewnętrznych, nie ma takiej metody.

Innymi słowy: musisz ręcznie zapętlić swoje obietnice i rozwiązać nową połączoną obietnicę, gdy tylko wszystkie obietnice zostaną rozliczone.


Zredagowałem swoje pytanie, aby wyjaśnić - ponieważ ES6 zawiera obietnice, chciałbym uniknąć używania innej biblioteki do tego, co moim zdaniem byłoby podstawową funkcjonalnością. Wydaje mi się, że dobrym miejscem na uzyskanie odpowiedzi byłoby skopiowanie źródła z jednej z obiecujących bibliotek.
Nathan Hagen
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.