2017-02-10 12 views
19

Wreszcie async/await będzie supported we wszystkich głównych przeglądarka wkrótce z wyjątkiem IE. Teraz możemy zacząć pisać bardziej czytelny kod z async/await, ale jest haczyk. Wiele osób korzysta z asynchroniczny czekają tak:Jak uruchomić async/czekać równolegle w JavaScript

const userResponse = await fetchUserAsync(); 
const postsResponse = await fetchPostsAsync(); 

Podczas gdy ten kod jest czytelny ona ma problem, to uruchamia funkcje w serii, to nie rozpocznie pobieranie posty aż sprowadzanie użytkownika jest zakończona. Rozwiązania są proste, musimy pobierać zasoby równolegle.

Więc to, co chcę zrobić, to (w języku pseudo):

fn task() { 
    result-1 = doAsync(); 
    result-2 = doAsync(); 
    result-n = doLongAsync(); 

    // handle results together 
    combinedResult = handleResults(result-1, result-2); 

    lastResult = handleLastResult(result-n); 
} 

Odpowiedz

43

można napisać coś takiego:

const responses = await Promise.all([ 
fetchUserAsync(), 
fetchPostsAsync(), 
]); 

const userResponse = responses[0]; 
const postsResponse = responses[1]; 

jest to łatwe, prawda? Ale jest w tym haczyk. Promise.all ma niepowodzenie szybkie zachowanie co oznacza, że ​​odrzuci, gdy tylko jedna z obietnic zostanie odrzucona. Prawdopodobnie chcesz bardziej niezawodne rozwiązanie, w którym jesteśmy odpowiedzialni za obsługę odrzucenia któregokolwiek z pobrań. Na szczęście istnieje rozwiązanie, można to osiągnąć po prostu z async/await bez potrzeby korzystania z Promise.all. A Przykład pracy:

console.clear(); 
 

 
function wait(ms, data) { 
 
    return new Promise(resolve => setTimeout(resolve.bind(this, data), ms)); 
 
} 
 

 
/** 
 
* This will run in series, because 
 
* we call a function and immediately wait for it's result, 
 
* so this will finish in 1s. 
 
*/ 
 
async function series() { 
 
    return { 
 
    result1: await wait(500, 'seriesTask1'), 
 
    result2: await wait(500, 'seriesTask2'), 
 
    } 
 
} 
 

 
/** 
 
* While here we call the functions first, 
 
* then wait for the result later, so 
 
* this will finish in 500ms. 
 
*/ 
 
async function parallel() { 
 
    const task1 = wait(500, 'parallelTask1'); 
 
    const task2 = wait(500, 'parallelTask2'); 
 

 
    return { 
 
    result1: await task1, 
 
    result2: await task2, 
 
    } 
 
} 
 

 
async function taskRunner(fn, label) { 
 
    const startTime = performance.now(); 
 
    console.log(`Task ${label} starting...`); 
 
    let result = await fn(); 
 
    console.log(`Task ${label} finished in ${ Number.parseInt(performance.now() - startTime) } miliseconds with,`, result); 
 
} 
 

 
void taskRunner(series, 'series'); 
 
void taskRunner(parallel, 'parallel'); 
 

 

 
/* 
 
* The result will be: 
 
* Task series starting... 
 
* Task parallel starting... 
 
* Task parallel finished in 500 milliseconds with, { "result1": "parallelTask1", "result2": "parallelTask2" } 
 
* Task series finished in 1001 milliseconds with, { "result1": "seriesTask1", "result2": "seriesTask2" } 
 
*/

Uwaga: Musisz przeglądarkę, która ma async/awaitenabled, aby uruchomić ten fragment (lub nodejs v7 i powyżej)

W ten sposób można używać po prostu try/catch radzić sobie z błędami i zwracać częściowe wyniki wewnątrz funkcji parallel.

+2

ładny kawałek kodu tam! Bardzo pomocna –

+0

Mam kilka pytań na temat Twojego kodu. Jak możesz "skomponować" oczekiwaną pulę równoległą. W twoim przykładzie wiesz, że masz dwa zadania do wykonania i zwrócenia wyniku. W jaki sposób mogę na przykład komponować przy użyciu pętli for (powiedzmy, że nie wiem, jak liczą się zadania w momencie pisania skryptu). Mój przypadek użycia: pobieram niektóre identyfikatory z wywołania HTTP, a następnie dla każdego identyfikatora mam zadanie do uruchomienia. Jak równolegle uruchomić wszystkie zadania dla pobranego identyfikatora? – BlackHoleGalaxy

+0

Dzięki, dobre pytanie! Zaktualizuję odpowiedź, gdy znajduję się przed moim komputerem Mac. – NoNameProvided

0

Po prostu zrobiłem to samo. Używając obietnic, a następnie Promise.all, aby zsynchronizować je na końcu, możesz wykonać wiele jednoczesnych żądań, ale upewnij się, że masz wszystkie wyniki z powrotem przed zakończeniem.

Zobacz tutaj w ostatnim przykładzie: http://javascriptrambling.blogspot.com/2017/04/to-promised-land-with-asyncawait-and.html

+0

Jak już powiedziałem, Promise.all ma szybkie działanie, więc nie archiwizujesz tego samego celu. – NoNameProvided

+0

Ah, nie zdawałem sobie sprawy, że zadałeś własne pytanie, żebyś mógł na nie odpowiedzieć. Ale w pierwotnym pytaniu nie wspomniałeś, że chciałeś poradzić sobie z każdym błędem. Zwykle wystarczy obsłużyć tylko jeden błąd, ponieważ rzadko zdarza się, aby proces zakończył się powodzeniem, nawet jeśli jedna część ulegnie awarii. O ile nie jest to potrzebne, mniej kodu jest lepiej. –

9

Jeśli jesteś ok z fail-fast zachowania Promise.all i składni przypisania rozpad:

const [userResponse, postsResponse] = await Promise.all([ 
    fetchUserAsync(), 
    fetchPostsAsync(), 
]); 
0

Kod pseudo może być napisany poniżej:

fn async task() { 
    result-1 = doAsync(); 
    result-2 = doAsync(); 
    result-n = doLongAsync(); 
    try{ 
    // handle results together 
    combinedResult = handleResults(await result-1, await result-2); 
    lastResult = handleLastResult(await result-n); 
    } 
    catch(err){ 
    console.error(err) 
    } 

} 

wynik-1, wynik-2, wynik-n będzie działać równolegle. combinedResult i lastResult będą również działać równolegle. Jednak wartość combinedResult, czyli return funkcji handleResults, zostanie zwrócona, gdy wynik-1 i wynik-2 są dostępne, a wartość lastResult, np. HandleLastResult, zostanie zwrócona po uzyskaniu wyniku-n.

Nadzieja to pomaga

+0

możesz odnieść się do tego linku w celu lepszego zrozumienia: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function –

0

Po pierwsze, to kod blokowanie kodu?

Jeśli tak, pamiętaj, że javascript jest pojedynczym wątkiem, więc nie możesz uruchomić dwóch kodów synchronicznych, na przykład dwóch pętli (na czas lub w tym samym czasie) w tym samym czasie.

Ale możliwe jest, aby przy pomocy Workbench uzyskać , udało mi się wykonywać funkcje w ogólnych przeglądarkach internetowych i bez używania oddzielnych plików js.

setInterval(()=>{console.log("non blocked " + Math.random())}, 900) 
 

 
console.log("start blocking code in parallel in web Worker") 
 
console.time("blocked") 
 

 
genericWorker(window, ["blockCpu", function (block){  
 
    block(10000) //This blockCpu function is defined below 
 
    return "\n\nbla bla\n" //This is catched in the resolved promise 
 

 
}]).then(function (result){ 
 
    console.timeEnd("blocked") 
 
    console.log("End of blocking code", result) 
 
}) 
 
.catch(function(error) { console.log(error) }) 
 

 

 
/* A Web Worker that does not use a File, it create that from a Blob 
 
    @cb_context, The context where the callback functions arguments are, ex: window 
 
    @cb, ["fn_name1", "fn_name2", function (fn1, fn2) {}] 
 
     The callback will be executed, and you can pass other functions to that cb 
 
*/ 
 
function genericWorker(cb_context, cb) { 
 
    return new Promise(function (resolve, reject) { 
 

 
     if (!cb || !Array.isArray(cb)) 
 
      return reject("Invalid data") 
 

 
     var callback = cb.pop() 
 
     var functions = cb 
 

 
     if (typeof callback != "function" || functions.some((fn)=>{return typeof cb_context[fn] != "function"})) 
 
      return reject(`The callback or some of the parameters: (${functions.toString()}) are not functions`) 
 

 
     if (functions.length>0 && !cb_context) 
 
      return reject("context is undefined") 
 

 
     callback = fn_string(callback) //Callback to be executed 
 
     functions = functions.map((fn_name)=> { return fn_string(cb_context[fn_name]) }) 
 

 
     var worker_file = window.URL.createObjectURL(new Blob(["self.addEventListener('message', function(e) { var bb = {}; var args = []; for (fn of e.data.functions) { bb[fn.name] = new Function(fn.args, fn.body); args.push(fn.name)}; var callback = new Function(e.data.callback.args, e.data.callback.body); args = args.map(function(fn_name) { return bb[fn_name] }); var result = callback.apply(null, args) ;self.postMessage(result);}, false)"])) 
 
     var worker = new Worker(worker_file) 
 

 
     worker.postMessage({ callback: callback, functions: functions }) 
 

 
     worker.addEventListener('error', function(error){ return reject(error.message) }) 
 

 
     worker.addEventListener('message', function(e) { 
 
      resolve(e.data), worker.terminate() 
 
     }, false) 
 

 
     //From function to string, with its name, arguments and its body 
 
     function fn_string (fn) { 
 
      var name = fn.name, fn = fn.toString() 
 

 
      return { name: name, 
 
       args: fn.substring(fn.indexOf("(") + 1, fn.indexOf(")")), 
 
       body: fn.substring(fn.indexOf("{") + 1, fn.lastIndexOf("}")) 
 
      } 
 
     } 
 
    }) 
 
} 
 

 
//random blocking function 
 
function blockCpu(ms) { 
 
    var now = new Date().getTime(), result = 0 
 
    while(true) { 
 
     result += Math.random() * Math.random(); 
 
     if (new Date().getTime() > now +ms) 
 
      return; 
 
    } 
 
}