Jak zwrócić wiele obietnic i poczekać na nie wszystkie, zanim zrobisz inne rzeczy

87

Mam pętlę, która wywołuje metodę, która robi rzeczy asynchronicznie. Ta pętla może wywołać metodę wiele razy. Po tej pętli mam kolejną pętlę, która musi zostać wykonana tylko wtedy, gdy wszystkie czynności asynchroniczne są wykonane.

To ilustruje to, czego chcę:

for (i = 0; i < 5; i++) {
    doSomeAsyncStuff();    
}

for (i = 0; i < 5; i++) {
    doSomeStuffOnlyWhenTheAsyncStuffIsFinish();    
}

Nie znam obietnic, więc czy ktoś mógłby mi w tym pomóc?

Oto jak moje doSomeAsyncStuff()zachowanie:

function doSomeAsyncStuff() {
    var editor = generateCKEditor();
    editor.on('instanceReady', function(evt) {
        doSomeStuff();
        // There should be the resolve() of the promises I think.
    })
}

Może muszę zrobić coś takiego:

function doSomeAsyncStuff() {
    var editor = generateCKEditor();
    return new Promise(function(resolve,refuse) {
        editor.on('instanceReady', function(evt) {
            doSomeStuff();
            resolve(true);
        });
    });
}

Ale nie jestem pewien składni.

Ganbin
źródło
Czy kontrolujesz wywołania asynchroniczne? Czy oni już składają obietnice, czy też możesz sprawić, że zwrócą obietnice?
TJ Crowder
Jaka dokładnie jest sekwencja? Czy musisz wywołać inne funkcje po zakończeniu wszystkich poprzednich funkcji asynchronicznych? A może wystarczy wywołać funkcję po zakończeniu każdej asynchronicznej?
Sosdoc
Na razie pierwsza funkcja nie zwraca obietnic. To muszę wdrożyć. Chcę edytować moją wiadomość, aby dodać kilka szczegółów dotyczących przepływu moich funkcji. I tak, potrzebuję, aby wszystkie elementy pierwszej pętli zostały zakończone przed rozpoczęciem wykonywania rzeczy w drugiej pętli.
Ganbin
1
Jeśli chodzi o twoją edycję: „Może muszę zrobić coś takiego” Tak, bardzo podobnie, z wyjątkiem tego, że sna końcu nie ma Promise.
TJ Crowder,

Odpowiedzi:

163

Możesz użyć Promise.all( specyfikacja , MDN ) do tego: akceptuje kilka indywidualnych obietnic i zwraca jedną obietnicę, która jest rozwiązana, gdy wszystkie z nich zostały rozwiązane lub odrzucona, gdy któraś z nich zostanie odrzucona.

Więc jeśli złożysz doSomeAsyncStuffobietnicę zwrotu, to:

    const promises = [];
//  ^^^^^−−−−−−−−−−−−−−−−−−−−−−−−−−− use `const` or `let`, not `var`
    
    for (let i = 0; i < 5; i++) {
//       ^^^−−−−−−−−−−−−−−−−−−−−−−−− added missing declaration
        promises.push(doSomeAsyncStuff());
    }
    
    Promise.all(promises)
        .then(() => {
            for (let i = 0; i < 5; i++) {
//               ^^^−−−−−−−−−−−−−−−− added missing declaration
                doSomeStuffOnlyWhenTheAsyncStuffIsFinish();    
            }
        })
        .catch((e) => {
            // handle errors here
        });

MDN ma tutaj artykuł o obietnicach . Szczegółowo omawiam również obietnice w rozdziale 8 mojej książki JavaScript: The New Toys , linki w moim profilu, jeśli jesteś zainteresowany.

Oto przykład:

 function doSomethingAsync(value) {
     return new Promise((resolve) => {
         setTimeout(() => {
             console.log("Resolving " + value);
             resolve(value);
         }, Math.floor(Math.random() * 1000));
     });
   }
   
   function test() {
       const promises = [];
       
       for (let i = 0; i < 5; ++i) {
           promises.push(doSomethingAsync(i));
       }
       
       Promise.all(promises)
           .then((results) => {
               console.log("All done", results);
           })
           .catch((e) => {
               // Handle errors here
           });
   }
   
   test();

Przykładowe dane wyjściowe (ze względu na to Math.random, co kończy się najpierw, może się różnić):

Rozpatrywanie 3
Rozpatrywanie 2
Rozpatrywanie 1
Rozpatrywanie 4
Rozpatrywanie 0
Wszystko gotowe [0,1,2,3,4]
TJ Crowder
źródło
Ok, dzięki, próbuję teraz i za kilka minut przyjdę z opinią.
Ganbin
12
Wow, wielkie dzięki, teraz znacznie lepiej rozumiem obietnice, dużo czytałem o obietnicach, ale dopóki nie będziemy musieli ich użyć w prawdziwym kodzie, tak naprawdę nie rozumiemy wszystkich mechanizmów. Teraz jest mi lepiej i dzięki tobie mogę zacząć pisać fajne rzeczy.
Ganbin,
1
Ponadto, jeśli chcesz, aby te zadania zostały wykonane w kolejności z dowolnego powodu (na przykład kpiny z postępów), możesz zmienić Math.floor(Math.random() * 1000)na(i * 1000)
OK,
@TJ teraz, jak mogę renderować dane wynikowe do widoku i tam mogę zrobić pętlę, aby wyświetlić dane
Ajit Singh
1
@ user1063287 - Możesz to zrobić, jeśli kod jest w kontekście, w którym awaitjest to dozwolone. W tej chwili jedynym miejscem, którego możesz użyć, awaitjest wnętrze asyncfunkcji. (W pewnym momencie będziesz mógł go również używać na najwyższym poziomie modułów.)
TJ Crowder,
5

Funkcja wielokrotnego użytku działa dobrze dla tego wzorca:

function awaitAll(count, asyncFn) {
  const promises = [];

  for (i = 0; i < count; ++i) {
    promises.push(asyncFn());
  }

  return Promise.all(promises);
}

Przykład OP:

awaitAll(5, doSomeAsyncStuff)
  .then(results => console.log('doSomeStuffOnlyWhenTheAsyncStuffIsFinished', results))
  .catch(e => console.error(e));

Powiązany wzorzec to iteracja po tablicy i wykonanie operacji asynchronicznej na każdym elemencie:

function awaitAll(list, asyncFn) {
  const promises = [];

  list.forEach(x => {
    promises.push(asyncFn(x));
  });

  return Promise.all(promises);
}

Przykład:

const books = [{ id: 1, name: 'foo' }, { id: 2, name: 'bar' }];

function doSomeAsyncStuffWith(book) {
  return Promise.resolve(book.name);
}

awaitAll(books, doSomeAsyncStuffWith)
  .then(results => console.log('doSomeStuffOnlyWhenTheAsyncStuffIsFinished', results))
  .catch(e => console.error(e));
2 Ropucha
źródło
1
To naprawdę sprawia, że ​​kod jest łatwiejszy do zrozumienia i czystszy. Nie sądzę, aby obecny przykład (który został oczywiście dostosowany do kodu OP) oddaje tę sprawiedliwość. To niezła sztuczka, dzięki!
Shaun Vermaak
2
const doSomeAsyncStuff = async (funcs) => {
  const allPromises = funcs.map(func => func());
  return await Promise.all(allPromises);
}

doSomeAsyncStuff([
  () => new Promise(resolve => setTimeout(() => resolve(), 100)),
  () => new Promise(resolve => setTimeout(() => resolve(), 100)),
  () => new Promise(resolve => setTimeout(() => resolve(), 100)),
  () => new Promise(resolve => setTimeout(() => resolve(), 100)),
  () => new Promise(resolve => setTimeout(() => resolve(), 100)),
]);
JoeTidee
źródło
1

Oto kod, który napisałem dla siebie, aby zrozumieć podane tutaj odpowiedzi. Mam zapytania mangusty w pętli for, więc wstawiam tutaj, asyncFunctionaby zająć jego miejsce. Mam nadzieję, że to pomoże każdemu. Możesz uruchomić ten skrypt w węźle lub w dowolnym z wielu środowisk wykonawczych Javascript.

let asyncFunction = function(value, callback)
{
        setTimeout(function(){console.log(value); callback();}, 1000);
}



// a sample function run without promises

asyncFunction(10,
    function()
    {
        console.log("I'm back 10");
    }
);


//here we use promises

let promisesArray = [];

let p = new Promise(function(resolve)
{
    asyncFunction(20,
        function()
        {
            console.log("I'm back 20");
            resolve(20);
        }
    );
});

promisesArray.push(p);


for(let i = 30; i < 80; i += 10)
{
    let p = new Promise(function(resolve)
    {
        asyncFunction(i,
            function()
            {
                console.log("I'm back " + i);
                resolve(i);
            }
        );
    });
    promisesArray.push(p);
}


// We use Promise.all to execute code after all promises are done.

Promise.all(promisesArray).then(
    function()
    {
        console.log("all promises resolved!");
    }
)
Mina Michael
źródło
0

/*** Worst way ***/
for(i=0;i<10000;i++){
  let data = await axios.get(
    "https://yourwebsite.com/get_my_data/"
  )
  //do the statements and operations
  //that are dependant on data
}

//Your final statements and operations
//That will be performed when the loop ends

//=> this approach will perform very slow as all the api call
// will happen in series


/*** One of the Best way ***/

const yourAsyncFunction = async (anyParams) => {
  let data = await axios.get(
    "https://yourwebsite.com/get_my_data/"
  )
  //all you statements and operations here
  //that are dependant on data
}
var promises = []
for(i=0;i<10000;i++){
  promises.push(yourAsyncFunction(i))
}
await Promise.all(promises)
//Your final statement / operations
//that will run once the loop ends

//=> this approach will perform very fast as all the api call
// will happen in parallal

Sourav Purkait
źródło