Tìm kiếm trang web

javascript - Gọi các hàm async/await song song


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

await someCall();
await anotherCall();

Liệu tôi có hiểu đúng rằng anotherCall() chỉ được gọi khi someCall() đã hoàn tất không? Cách tinh tế 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 chờ đợi lời hứa không song song trong trình tạo không đồng bộ, vì nó sử dụng trình tạo và tôi đang hỏi về trường hợp sử dụng tổng quát hơn.

Câu trả lời #1

Bạn có thể chờ trên Promise.all():

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

Để lưu kết quả:

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

Lưu ý rằng Promise.all thất bại nhanh chóng, điều này có nghĩa là ngay khi một trong những promise được cung cấp cho nó bị từ chối, thì toàn bộ sẽ bị 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ờ tất cả các lời hứa (promises) hoặc được thực hiện hoặc bị 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 thức này một cách tự nhiên.

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

Lưu ý: Nếu bạn sử dụng Promise.all, các hành động đã hoàn thành trước khi xảy ra từ chối sẽ không được hoàn lại, vì vậy bạn có thể cần phải xử lý tình huống như vậy. Ví dụ, nếu bạn có 5 hành động, 4 hành động nhanh, 1 hành động chậm và hành động chậm bị từ chối. Bốn hành động nhanh có thể đã được thực thi, vì vậy bạn có thể cần phải hoàn lại. Trong tình huống như vậy, hãy cân nhắc sử dụng Promise.allSettled vì nó sẽ cung cấp chi tiết chính xác hành động nào thất bại và hành động nào không.

Câu trả lời #2

Tóm tắt

Sử dụng Promise.all cho các cuộc gọi hàm song song, hành vi của kết quả không đúng khi xảy ra lỗi.

Đầu tiên, thực hiện tất cả các cuộc gọi bất đồng bộ cùng một lúc và lấy tất cả các đối tượng Promise. Thứ hai, sử dụng await trên các đối tượng Promise. Bằng cách này, trong khi bạn chờ Promise đầu tiên được giải quyết, các cuộc gọi bất đồng bộ khác vẫn tiếp tục tiến triển. Tổng thể, bạn chỉ phải chờ lâu bằng cuộc gọi bất đồ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ụ JSbin: http://jsbin.com/xerifanima/edit?js,console

Cảnh báo: Không quan trọng nếu các lệnh await nằm trên cùng một dòng hay các dòng khác nhau, miễn là lệnh await đầu tiên xảy ra sau tất cả các lệnh bất đồng bộ khác. Xem chú thích của JohnnyHK.

Cập nhật: câu trả lời này có thời điểm xử lý lỗi khác nhau theo câu trả lời của @bergi, nó KHÔNG ném ra lỗi ngay khi lỗi xảy ra mà là sau khi tất cả các promise được thực thi. Tôi so sánh kết quả với mẹo của @jonny: [result1, result2]=Promise.all([async1(), async2()]), xem đ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();
})();

Câu trả lời #3

Cập nhật:

Câu trả lời gốc khiến việc xử lý từ chối promise trở nên khó khăn (và trong một số trường hợp là không thể). Giải pháp đúng 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 hàm trước khi chờ bất kỳ hàm nào:

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

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

Câu trả lời số 4

Có một cách khác mà không cần Promise.all() để làm việc này song song:

Đầu tiên, chúng ta có 2 hàm để 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à theo thứ tự:

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

Đâ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

Câu trả lời số 5

Trong trường hợp của tôi, tôi có một số tác vụ mà tôi muốn thực hiện song song, nhưng tôi cần làm điều gì đó khác với kết quả của những tác 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

(async function(){
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');
})();

Câu trả lời số 6

Tôi đã tạo một gist thử nghiệm một số cách khác nhau để giải quyết các promise, kèm theo kết quả. Nó có thể hữu ích để xem những lựa chọn nào hoạt động.

Chỉnh sửa: Nội dung Gist theo nhận xét của Jin Lee

// Simple gist to test parallel promise resolution when using async / await

function promiseWait(time) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(true);
    }, time);
});
}


async function test() {
    return [
    await promiseWait(1000),
    await promiseWait(5000),
    await promiseWait(9000),
    await promiseWait(3000),
    ]
}

async function test2() {
    return {
        'aa': await promiseWait(1000),
        'bb': await promiseWait(5000),
        'cc': await promiseWait(9000),
        'dd': await promiseWait(3000),
    }
}

async function test3() {
    return await {
        'aa': promiseWait(1000),
        'bb': promiseWait(5000),
        'cc': promiseWait(9000),
        'dd': promiseWait(3000),
    }
}

async function test4() {
    const p1 =  promiseWait(1000);
    const p2 =  promiseWait(5000);
    const p3 =  promiseWait(9000);
    const p4 =  promiseWait(3000);
    return {
        'aa': await p1,
        'bb': await p2,
        'cc': await p3,
        'dd': await p4,
    };
}

async function test5() {
    return await Promise.all([
                             await promiseWait(1000),
                             await promiseWait(5000),
                             await promiseWait(9000),
                             await promiseWait(3000),
                             ]);
}

async function test6() {
    return await Promise.all([
                             promiseWait(1000),
                             promiseWait(5000),
                             promiseWait(9000),
                             promiseWait(3000),
                             ]);
}

async function test7() {
    const p1 =  promiseWait(1000);
    const p2 =  promiseWait(5000);
    const p3 =  promiseWait(9000);
    return {
        'aa': await p1,
        'bb': await p2,
        'cc': await p3,
        'dd': await promiseWait(3000),
    };
}

let start = Date.now();

test().then((res) => {
    console.log('Test Done, elapsed', (Date.now() - start) / 1000, res);

    start = Date.now();
    test2().then((res) => {
        console.log('Test2 Done, elapsed', (Date.now() - start) / 1000, res);

        start = Date.now();
        test3().then((res) => {
            console.log('Test3 Done, elapsed', (Date.now() - start) / 1000, res);

            start = Date.now();
            test4().then((res) => {
                console.log('Test4 Done, elapsed', (Date.now() - start) / 1000, res);

                start = Date.now();
                test5().then((res) => {
                    console.log('Test5 Done, elapsed', (Date.now() - start) / 1000, res);

                    start = Date.now();
                    test6().then((res) => {
                        console.log('Test6 Done, elapsed', (Date.now() - start) / 1000, res);
                    });

                    start = Date.now();
                    test7().then((res) => {
                        console.log('Test7 Done, elapsed', (Date.now() - start) / 1000, res);
                    });
                });
            });

        });
    });

});
/*
Test Done, elapsed 18.006 [ true, true, true, true ]
Test2 Done, elapsed 18.009 { aa: true, bb: true, cc: true, dd: true }
Test3 Done, elapsed 0 { aa: Promise { <pending> },
  bb: Promise { <pending> },
  cc: Promise { <pending> },
  dd: Promise { <pending> } }
Test4 Done, elapsed 9 { aa: true, bb: true, cc: true, dd: true }
Test5 Done, elapsed 18.008 [ true, true, true, true ]
Test6 Done, elapsed 9.003 [ true, true, true, true ]
Test7 Done, elapsed 12.007 { aa: true, bb: true, cc: true, dd: true }
*/

Bài viết liên quan