Wywoływanie funkcji asynchronicznych / oczekujących równolegle

Z tego co rozumiem, w ES7/ES2016 umieszczanie wielu await w kodzie będzie działać podobnie do łączenia .then() z obietnicami, co oznacza, że będą one wykonywane jeden po drugim, a nie równolegle. Tak więc, na przykład, mamy ten kod:

await someCall();
await anotherCall();

Czy dobrze rozumiem, że anotherCall() będzie wywołane tylko wtedy, gdy someCall() zostanie zakończone? Jaki jest najbardziej elegancki sposób nazywania ich równolegle?

Chcę go użyć w Node, więc może jest rozwiązanie z async biblioteka?

EDIT: nie jestem zadowolony z rozwiązania podanego w tym pytaniu: spowolnienie z powodu nie-równoległego oczekiwania na obietnice w generatorach asynchronicznych , ponieważ używa generatorów i pytam o bardziej ogólny przypadek użycia.

Author: David Ferenczy Rogožan, 2016-02-24

10 answers

Możesz czekać na Promise.all():

await Promise.all([someCall(), anotherCall()]);

Aby zapisać wyniki:

let [someResult, anotherResult] = await Promise.all([someCall(), anotherCall()]);

Zauważ, że Promise.all szybko się nie powiedzie, co oznacza, że jak tylko jedna z obietnic mu dostarczona odrzuci, to cała rzecz odrzuci.

const happy = (v, ms) => new Promise((resolve) => setTimeout(() => resolve(v), ms))
const sad = (v, ms) => new Promise((_, reject) => setTimeout(() => reject(v), ms))

Promise.all([happy('happy', 100), sad('sad', 50)])
  .then(console.log).catch(console.log) // 'sad'

Jeśli zamiast tego chcesz poczekać na spełnienie lub odrzucenie wszystkich obietnic, możesz użyć Promise.allSettled. Należy pamiętać, że Internet Explorer nie obsługuje natywnie tego metoda.

const happy = (v, ms) => new Promise((resolve) => setTimeout(() => resolve(v), ms))
const sad = (v, ms) => new Promise((_, reject) => setTimeout(() => reject(v), ms))

Promise.allSettled([happy('happy', 100), sad('sad', 50)])
  .then(console.log) // [{ "status":"fulfilled", "value":"happy" }, { "status":"rejected", "reason":"sad" }]

Uwaga: jeśli użyjesz Promise.all akcji, które udało się zakończyć przed odrzuceniem, nie zostaną wycofane, więc być może będziesz musiał zająć się taką sytuacją. Na przykład jeśli masz 5 akcji, 4 szybkie, 1 powolne i powolne odrzucenia. Te 4 akcje mogą być już wykonane, więc może być konieczne cofnięcie. W takiej sytuacji rozważ użycie Promise.allSettled, podczas gdy dostarczy ona dokładnych szczegółów, które działanie zakończyło się niepowodzeniem, a które nie.

 862
Author: madox2,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-12-08 20:16:37

TL; DR

Użyj Promise.all dla równoległych wywołań funkcji, zachowanie odpowiedzi nie jest prawidłowe, gdy wystąpi błąd.


Najpierw wykonaj Wszystkie asynchroniczne wywołania naraz i uzyskaj wszystkie Promise obiekty. Po drugie, użyj await na obiektach Promise. W ten sposób, podczas oczekiwania na pierwsze Promise, aby rozwiązać inne asynchroniczne wywołania, nadal postępują. Ogólnie rzecz biorąc, będziesz czekać tylko tak długo, jak najwolniejsze połączenie asynchroniczne. Na przykład:

// Begin first call and store promise without waiting
const someResult = someCall();

// Begin second call and store promise without waiting
const anotherResult = anotherCall();

// Now we await for both results, whose async processes have already been started
const finalResult = [await someResult, await anotherResult];

// At this point all calls have been resolved
// Now when accessing someResult| anotherResult,
// you will have a value instead of a promise

Jsbin przykład: http://jsbin.com/xerifanima/edit?js, konsola

Zastrzeżenie: nie ma znaczenia, czy wywołania await są na tej samej linii, czy na różnych liniach, o ile pierwsze wywołanie awaitnastąpi po wszystkich wywołaniach asynchronicznych. Zobacz komentarz Johnnyhka.


Update: ta odpowiedź ma inny czas obsługi błędów zgodnie z odpowiedzią @bergi, robi a nie rzucać out błąd jak błąd występuje, ale po wszystkie obietnice są wykonywane. Porównuję wynik z końcówką @ jonny: [result1, result2] = Promise.all([async1(), async2()]), sprawdź poniższy fragment kodu

const correctAsync500ms = () => {
  return new Promise(resolve => {
    setTimeout(resolve, 500, 'correct500msResult');
  });
};

const correctAsync100ms = () => {
  return new Promise(resolve => {
    setTimeout(resolve, 100, 'correct100msResult');
  });
};

const rejectAsync100ms = () => {
  return new Promise((resolve, reject) => {
    setTimeout(reject, 100, 'reject100msError');
  });
};

const asyncInArray = async (fun1, fun2) => {
  const label = 'test async functions in array';
  try {
    console.time(label);
    const p1 = fun1();
    const p2 = fun2();
    const result = [await p1, await p2];
    console.timeEnd(label);
  } catch (e) {
    console.error('error is', e);
    console.timeEnd(label);
  }
};

const asyncInPromiseAll = async (fun1, fun2) => {
  const label = 'test async functions with Promise.all';
  try {
    console.time(label);
    let [value1, value2] = await Promise.all([fun1(), fun2()]);
    console.timeEnd(label);
  } catch (e) {
    console.error('error is', e);
    console.timeEnd(label);
  }
};

(async () => {
  console.group('async functions without error');
  console.log('async functions without error: start')
  await asyncInArray(correctAsync500ms, correctAsync100ms);
  await asyncInPromiseAll(correctAsync500ms, correctAsync100ms);
  console.groupEnd();

  console.group('async functions with error');
  console.log('async functions with error: start')
  await asyncInArray(correctAsync500ms, rejectAsync100ms);
  await asyncInPromiseAll(correctAsync500ms, rejectAsync100ms);
  console.groupEnd();
})();
 127
Author: Haven,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-01-21 20:36:34

Update:

Oryginalna odpowiedź utrudnia (a w niektórych przypadkach uniemożliwia) poprawną obsługę odrzuceń obietnic. Poprawnym rozwiązaniem jest użycie Promise.all:

const [someResult, anotherResult] = await Promise.all([someCall(), anotherCall()]);

Oryginalna odpowiedź:

Upewnij się, że wywołujesz obie funkcje, zanim poczekasz na któreś z nich:

// Call both functions
const somePromise = someCall();
const anotherPromise = anotherCall();

// Await both promises    
const someResult = await somePromise;
const anotherResult = await anotherPromise;
 100
Author: Jonathan Potter,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-02-05 22:01:06

Jest inny sposób bez obietnicy.wszystkie (), aby zrobić to równolegle:

Najpierw mamy 2 funkcje do drukowania liczb:

function printNumber1() {
   return new Promise((resolve,reject) => {
      setTimeout(() => {
      console.log("Number1 is done");
      resolve(10);
      },1000);
   });
}

function printNumber2() {
   return new Promise((resolve,reject) => {
      setTimeout(() => {
      console.log("Number2 is done");
      resolve(20);
      },500);
   });
}

To jest sekwencyjne:

async function oneByOne() {
   const number1 = await printNumber1();
   const number2 = await printNumber2();
} 
//Output: Number1 is done, Number2 is done

To jest równoległe:

async function inParallel() {
   const promise1 = printNumber1();
   const promise2 = printNumber2();
   const number1 = await promise1;
   const number2 = await promise2;
}
//Output: Number2 is done, Number1 is done
 32
Author: user2883596,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-02-27 13:25:17

Stworzyłem gist testujący różne sposoby rozwiązywania obietnic, z wynikami. Pomocne może być zapoznanie się z opcjami, które działają.

 8
Author: SkarXa,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-25 07:06:26

W moim przypadku mam kilka zadań, które chcę wykonać równolegle, ale muszę zrobić coś innego z wynikiem tych zadań.

function wait(ms, data) {
    console.log('Starting task:', data, ms);
    return new Promise(resolve => setTimeout(resolve, ms, data));
}

var tasks = [
    async () => {
        var result = await wait(1000, 'moose');
        // do something with result
        console.log(result);
    },
    async () => {
        var result = await wait(500, 'taco');
        // do something with result
        console.log(result);
    },
    async () => {
        var result = await wait(5000, 'burp');
        // do something with result
        console.log(result);
    }
]

await Promise.all(tasks.map(p => p()));
console.log('done');

I Wyjście:

Starting task: moose 1000
Starting task: taco 500
Starting task: burp 5000
taco
moose
burp
done
 4
Author: Alex Dresko,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-10-07 14:37:28

Czekam Na Obietnicę.all ([someCall (), anotherCall ()]); jak już wspomniano, będzie działać jako ogrodzenie wątku( bardzo często w kodzie równoległym jako CUDA), stąd pozwoli wszystkie obietnice w nim działać bez blokowania siebie, ale uniemożliwi kontynuowanie wykonywania, dopóki wszystkie nie zostaną rozwiązane.

Innym podejściem, które warto podzielić jest węzeł.js async, który pozwoli również łatwo kontrolować ilość współbieżności, która jest zwykle pożądana, jeśli zadanie jest bezpośrednio związane z wykorzystaniem ograniczone zasoby jak wywołanie API, operacje We/Wy itp.

// create a queue object with concurrency 2
var q = async.queue(function(task, callback) {
  console.log('Hello ' + task.name);
  callback();
}, 2);

// assign a callback
q.drain = function() {
  console.log('All items have been processed');
};

// add some items to the queue
q.push({name: 'foo'}, function(err) {
  console.log('Finished processing foo');
});

q.push({name: 'bar'}, function (err) {
  console.log('Finished processing bar');
});

// add some items to the queue (batch-wise)
q.push([{name: 'baz'},{name: 'bay'},{name: 'bax'}], function(err) {
  console.log('Finished processing item');
});

// add some items to the front of the queue
q.unshift({name: 'bar'}, function (err) {
  console.log('Finished processing bar');
});

Podziękowania dla autora artykułu (Czytaj więcej )

 3
Author: Thiago Conrado,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-05-18 00:34:47
    // A generic test function that can be configured 
    // with an arbitrary delay and to either resolve or reject
    const test = (delay, resolveSuccessfully) => new Promise((resolve, reject) => setTimeout(() => {
        console.log(`Done ${ delay }`);
        resolveSuccessfully ? resolve(`Resolved ${ delay }`) : reject(`Reject ${ delay }`)
    }, delay));

    // Our async handler function
    const handler = async () => {
        // Promise 1 runs first, but resolves last
        const p1 = test(10000, true);
        // Promise 2 run second, and also resolves
        const p2 = test(5000, true);
        // Promise 3 runs last, but completes first (with a rejection) 
        // Note the catch to trap the error immediately
        const p3 = test(1000, false).catch(e => console.log(e));
        // Await all in parallel
        const r = await Promise.all([p1, p2, p3]);
        // Display the results
        console.log(r);
    };

    // Run the handler
    handler();
    /*
    Done 1000
    Reject 1000
    Done 5000
    Done 10000
    */

Podczas gdy ustawienie p1, p2 i p3 nie jest ściśle uruchamiane równolegle, nie zatrzymują one żadnej realizacji i można przechwycić błędy kontekstowe za pomocą haczyka.

 2
Author: Thrunobulax,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-07-03 20:10:32

Tworzę funkcję pomocniczą waitAll, może to sprawi, że będzie słodsza. Na razie działa tylko w nodejs, nie w przeglądarce chrome.

    //const parallel = async (...items) => {
    const waitAll = async (...items) => {
        //this function does start execution the functions
        //the execution has been started before running this code here
        //instead it collects of the result of execution of the functions

        const temp = [];
        for (const item of items) {
            //this is not
            //temp.push(await item())
            //it does wait for the result in series (not in parallel), but
            //it doesn't affect the parallel execution of those functions
            //because they haven started earlier
            temp.push(await item);
        }
        return temp;
    };

    //the async functions are executed in parallel before passed
    //in the waitAll function

    //const finalResult = await waitAll(someResult(), anotherResult());
    //const finalResult = await parallel(someResult(), anotherResult());
    //or
    const [result1, result2] = await waitAll(someResult(), anotherResult());
    //const [result1, result2] = await parallel(someResult(), anotherResult());

 -5
Author: Fred Yang,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-02-21 00:50:51

Głosuję za:

await Promise.all([someCall(), anotherCall()]);

Bądź świadomy momentu wywołania funkcji, może to spowodować nieoczekiwany rezultat:

// Supposing anotherCall() will trigger a request to create a new User

if (callFirst) {
  await someCall();
} else {
  await Promise.all([someCall(), anotherCall()]); // --> create new User here
}

Ale podążanie zawsze wywołuje żądanie utworzenia nowego Użytkownika

// Supposing anotherCall() will trigger a request to create a new User

const someResult = someCall();
const anotherResult = anotherCall(); // ->> This always creates new User

if (callFirst) {
  await someCall();
} else {
  const finalResult = [await someResult, await anotherResult]
}
 -6
Author: Hoang Le Anh Tu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-28 09:59:51