Gọi các chức năng async / await song song


429

Theo như tôi hiểu, trong ES7 / ES2016, việc đặt nhiều awaitmã sẽ hoạt động tương tự như xâu chuỗi .then()với các lời hứa, nghĩa là chúng sẽ thực thi lần lượt từng chuỗi thay vì theo parallerl. Vì vậy, ví dụ, chúng tôi có mã này:

await someCall();
await anotherCall();

Tôi có hiểu chính xác nó anotherCall()sẽ chỉ được gọi khi someCall()hoàn thành không? Cách thanh lịch nhất để gọi chúng song song là gì?

Tôi muốn sử dụng nó trong Node, vì vậy có lẽ có một giải pháp với thư viện async?

EDIT: Tôi không hài lòng với giải pháp được cung cấp trong câu hỏi này: Chậm lại do không chờ đợi các lời hứa trong các trình tạo không đồng bộ , vì nó sử dụng các trình tạo và tôi đang hỏi về trường hợp sử dụng chung hơn.


1
@adeneo Điều đó không chính xác, Javascript không bao giờ chạy song song trong bối cảnh của chính nó.
Người mù67

5
@ Blindman67 - ít nhất là theo cách OP có nghĩa, trong đó hai hoạt động không đồng bộ đang chạy đồng thời, nhưng trong trường hợp này, điều tôi muốn viết là chúng chạy nối tiếp , đầu tiên awaitsẽ chờ chức năng đầu tiên hoàn thành hoàn toàn trước khi thực hiện lần thứ hai.
adeneo 24/2/2016

3
@ Blindman67 - đó là một luồng đơn, nhưng giới hạn đó không áp dụng cho các phương thức không đồng bộ, chúng có thể chạy đồng thời và trả về phản hồi khi chúng được thực hiện, nghĩa là OP có nghĩa là "parallell".
adeneo

7
@ Blindman67 - Tôi nghĩ rằng OP đang hỏi rõ ràng những gì OP đang hỏi, sử dụng mẫu async / await sẽ làm cho các chức năng chạy nối tiếp, ngay cả khi chúng không đồng bộ, do đó, lần đầu tiên sẽ hoàn thành trước khi thứ hai được gọi, v.v. hỏi làm thế nào để gọi cả hai hàm trong parallell và vì chúng rõ ràng không đồng bộ, mục đích là để chạy chúng đồng thời, ví dụ như trong parallell, ví dụ, thực hiện hai yêu cầu ajax, không phải là vấn đề trong javascript, vì hầu hết các phương thức async , như bạn đã lưu ý, chạy mã gốc và sử dụng nhiều luồng hơn.
adeneo

3
@Bergi đây không phải là một bản sao của câu hỏi được liên kết - đây là đặc biệt về cú pháp async / await và Promises gốc . Câu hỏi được liên kết liên quan đến thư viện bluebird với máy phát điện & năng suất. Khái niệm tương tự có lẽ, nhưng không thực hiện.
Iest

Câu trả lời:


694

Bạn có thể chờ đợi vào Promise.all():

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

Để lưu trữ kết quả:

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

Lưu ý rằng Promise.allthất bại nhanh, có nghĩa là ngay khi một trong những lời hứa được cung cấp cho nó từ chối, thì toàn bộ điều đó sẽ từ chối.

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'

Nếu, thay vào đó, bạn muốn chờ đợi tất cả các lời hứa sẽ thực hiện hoặc từ chối, thì bạn có thể sử dụng Promise.allSettled. Lưu ý rằng Internet Explorer không hỗ trợ phương pháp này.

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


78
Sạch sẽ nhưng hãy chú ý đến hành vi thất bại nhanh chóng của Promise.all. Nếu bất kỳ chức năng nào gây ra lỗi, Promise.all sẽ từ chối
NoNameProvided 8/2/2017

11
Bạn có thể xử lý kết quả một phần độc đáo với async / await, xem stackoverflow.com/a/42158854/2019689
NoNameProvided

131
Mẹo chuyên nghiệp: sử dụng tính năng hủy mảng để khởi tạo số lượng kết quả tùy ý từ Promise.all (), như:[result1, result2] = Promise.all([async1(), async2()]);
jonny

10
@jonny Đây có phải là chủ đề thất bại nhanh không? Ngoài ra, một người vẫn cần phải = await Promise.all?
theStherSide

5
@theUtherSide Bạn hoàn toàn đúng - Tôi bỏ qua việc bao gồm sự chờ đợi.
jonny

114

TL; DR

Sử dụng Promise.allcho các cuộc gọi chức năng song song, các hành vi trả lời không chính xác khi xảy ra lỗi.


Đầu tiên, thực hiện tất cả các cuộc gọi không đồng bộ cùng một lúc và có được tất cả các Promiseđối tượng. Thứ hai, sử dụng awaittrên các Promiseđối tượng. Bằng cách này, trong khi bạn chờ người đầu tiên Promisegiải quyết các cuộc gọi không đồng bộ khác vẫn đang tiến triển. Nhìn chung, bạn sẽ chỉ chờ chừng nào cuộc gọi không đồng bộ chậm nhất. Ví dụ:

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

Ví dụ về JSbin: http://jsbin.com/xerifanima/edit?js,console

Hãy cẩn thận: Sẽ không có vấn đề gì nếu các awaitcuộc gọi nằm trên cùng một dòng hoặc trên các dòng khác nhau, miễn là awaitcuộc gọi đầu tiên xảy ra sau tất cả các cuộc gọi không đồng bộ. Xem bình luận của JohnnyHK.


Cập nhật: câu trả lời này có thời gian xử lý lỗi khác nhau theo câu trả lời của @ bergi , nó KHÔNG loại bỏ lỗi khi xảy ra lỗi nhưng sau khi tất cả các lời hứa được thực hiện. Tôi so sánh kết quả với mẹo của @ jonny : [result1, result2] = Promise.all([async1(), async2()]), kiểm tra đoạn mã sau

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


11
Điều này có vẻ như là một tùy chọn đẹp hơn đối với tôi so với Promise.all - và với nhiệm vụ hủy hoại, bạn thậm chí có thể làm [someResult, anotherResult] = [await someResult, await anotherResult]nếu bạn đổi constsang let.
jawj

28
Nhưng điều này vẫn thực hiện các awaittuyên bố ser seri, phải không? Đó là, thực thi tạm dừng cho đến khi cái đầu tiên awaitgiải quyết, sau đó chuyển sang cái thứ hai. Promise.allthực hiện song song.
Andru

8
Cảm ơn bạn @Haven. Đây phải là câu trả lời được chấp nhận.
Stefan D

87
Câu trả lời này là sai lệch vì thực tế là cả hai chờ đợi được thực hiện trong cùng một dòng là không liên quan. Vấn đề là hai cuộc gọi không đồng bộ được thực hiện trước khi chờ đợi.
JohnnyHK

15
@Haven giải pháp này không giống như Promise.all. Nếu mỗi yêu cầu là một cuộc gọi mạng, await someResultsẽ cần phải được giải quyết trước khi await anotherResultbắt đầu. Ngược lại, trong Promise.allhai awaitcuộc gọi có thể được bắt đầu trước khi một trong hai cuộc gọi được giải quyết.
Ben Winding

89

Cập nhật:

Câu trả lời ban đầu gây khó khăn (và trong một số trường hợp là không thể) để xử lý chính xác các từ chối hứa hẹn. Giải pháp chính xác là sử dụng Promise.all:

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

Câu trả lời gốc:

Chỉ cần đảm bảo rằng bạn gọi cả hai chức năng trước khi chờ đợi một trong hai chức năng:

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

// Await both promises    
const someResult = await somePromise;
const anotherResult = await anotherPromise;

1
@JeffFischer Tôi đã thêm ý kiến ​​hy vọng làm cho nó rõ ràng hơn.
Jonathan Potter

9
Tôi cảm thấy như đây chắc chắn là câu trả lời thuần khiết nhất
Gershom

1
Câu trả lời này rõ ràng hơn nhiều so với Haven. Rõ ràng là các lệnh gọi hàm sẽ trả về các đối tượng hứa hẹn và awaitsau đó sẽ giải quyết chúng thành các giá trị thực.
dùng1032613

3
Điều này dường như làm việc trong một cái nhìn lướt qua, nhưng có vấn đề khủng khiếp với sự từ chối không được giải quyết . Đừng dùng cái này!
Bergi

1
@Bergi Bạn nói đúng, cảm ơn vì đã chỉ ra điều đó! Tôi đã cập nhật câu trả lời với một giải pháp tốt hơn.
Jonathan Potter

24

Có một cách khác mà không có Promise.all () để thực hiện song song:

Đầu tiên, chúng ta có 2 chức năng để in số:

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

Đây là tuần tự:

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

Điều này là song song:

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

10

Điều này có thể được thực hiện với Promise.allSettled () , tương tự Promise.all()nhưng không có hành vi không nhanh.

async function failure() {
    throw "Failure!";
}

async function success() {
    return "Success!";
}

const [failureResult, successResult] = await Promise.allSettled([failure(), success()]);

console.log(failureResult); // {status: "rejected", reason: "Failure!"}
console.log(successResult); // {status: "fulfilled", value: "Success!"}

Lưu ý : Đây là một tính năng cạnh chảy máu với sự hỗ trợ trình duyệt giới hạn, vì vậy tôi mạnh mẽ khuyên bạn đưa polyfill cho chức năng này.


7

Tôi đã tạo ra một ý chính thử nghiệm một số cách khác nhau để giải quyết các lời hứa, với kết quả. Nó có thể hữu ích để xem các tùy chọn làm việc.


Các thử nghiệm 4 và 6 trong ý chính đã trả về kết quả mong đợi. Xem stackoverflow.com/a/42158854/5683904 của NoNameProvided, người giải thích sự khác biệt giữa các tùy chọn.
akraines

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

Trong khi cài đặt p1, p2 và p3 không thực sự chạy chúng song song, chúng không giữ bất kỳ thực thi nào và bạn có thể bẫy các lỗi theo ngữ cảnh với một lệnh bắt.


2
Chào mừng bạn đến với Stack Overflow. Mặc dù mã của bạn có thể cung cấp câu trả lời cho câu hỏi, vui lòng thêm ngữ cảnh xung quanh nó để những người khác sẽ có một số ý tưởng về những gì nó làm và tại sao nó lại ở đó.
Theo

1

đang chờ Promise.all ([someCall (), AnotherCall ()]); như đã đề cập sẽ hoạt động như một hàng rào luồng (rất phổ biến trong mã song song là CUDA), do đó nó sẽ cho phép tất cả các lời hứa trong đó chạy mà không chặn nhau, nhưng sẽ ngăn việc thực thi tiếp tục cho đến khi TẤT CẢ được giải quyết.

một cách tiếp cận khác đáng để chia sẻ là Node.js async cũng sẽ cho phép bạn dễ dàng kiểm soát lượng đồng thời thường mong muốn nếu tác vụ được liên kết trực tiếp với việc sử dụng tài nguyên giới hạn như các thao tác API, I / O, Vân vân.

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

Tín dụng cho tự động bài viết trung bình ( đọc thêm )


0

Trong trường hợp của tôi, tôi có một số nhiệm vụ tôi muốn thực hiện song song, nhưng tôi cần phải làm một cái gì đó khác với kết quả của những nhiệm vụ đó.

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

Và đầu ra:

Starting task: moose 1000
Starting task: taco 500
Starting task: burp 5000
taco
moose
burp
done

-5

Tôi bỏ phiếu cho:

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

Hãy nhận biết thời điểm bạn gọi các chức năng, nó có thể gây ra kết quả không mong muốn:

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

Nhưng theo sau luôn kích hoạt yêu cầu tạo Người dùng mới

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

Vì bạn khai báo hàm bên ngoài / trước khi kiểm tra điều kiện và gọi chúng. Hãy thử gói chúng trong elsekhối.
Haven

@Haven: Ý tôi là khi bạn phân tách các khoảnh khắc bạn gọi hàm và chờ đợi có thể dẫn đến kết quả không mong muốn, ví dụ: async HTTP request.
Hoàng Lê Anh Từ

-6

Tôi tạo một hàm trợ giúp WaitAll, có thể nó có thể làm cho nó ngọt hơn. Nó chỉ hoạt động trong nodejs bây giờ, không phải trong trình duyệt 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());

3
Không, song song không xảy ra ở đây. Các forvòng lặp tuần tự đang chờ đợi từng lời hứa và cho biết thêm kết quả vào mảng.
Szczepan Hołyszewski

Tôi hiểu điều này dường như không làm việc cho mọi người. Vì vậy, tôi đã thử nghiệm trong node.js và trình duyệt. Thử nghiệm được thông qua trong node.js (v10, v11), firefox, nó không hoạt động trong trình duyệt chrome. Trường hợp thử nghiệm là trong gist.github.com/fredyang/ea736a7b8293edf7a1a25c39c7d2fbbf
Fred Yang

2
Tôi từ chối tin điều này. Không có gì trong tiêu chuẩn nói rằng các lần lặp khác nhau của vòng lặp for có thể được song song tự động; đây không phải là cách javascript hoạt động. Cách viết mã vòng lặp, nó có nghĩa như sau: "đang chờ một mục (exprwait apr), THEN đẩy kết quả đến temp, THEN lấy mục tiếp theo (lần lặp tiếp theo của vòng lặp for)." Đang chờ "cho mỗi mục hoàn toàn Nếu chỉ kiểm tra cho thấy có sự song song hóa thì đó phải là do bộ chuyển đổi đang làm một cái gì đó không đạt tiêu chuẩn hoặc bị lỗi.
Szczepan Hołyszewski 19/2/19

@ SzczepanHołyszewski Sự tự tin của bạn về việc từ chối mà không chạy trường hợp thử nghiệm truyền cảm hứng cho tôi để thực hiện một số đổi tên phản hồi và nhận xét thêm. Tất cả các mã là ES6 cũ đơn giản, không cần phải chuyển mã.
Fred Yang

Không chắc chắn tại sao điều này bị hạ thấp rất nhiều. Về cơ bản, đó là cùng một câu trả lời mà @ user2883596 đã đưa ra.
Jonathan Sudiaman
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.