ค้นหาเว็บไซต์

javascript - เรียกฟังก์ชัน async/await พร้อมกัน


เท่าที่ผมเข้าใจ ใน ES7/ES2016 การใส่ await หลายตัวในโค้ดจะทำงานคล้ายกับการเชน .then() กับ promises ซึ่งหมายความว่าพวกมันจะทำงานทีละตัว แทนที่จะทำงานพร้อมกัน ดังนั้น ตัวอย่างเช่น เรามีโค้ดนี้:

await someCall();
await anotherCall();

ฉันเข้าใจถูกต้องหรือไม่ว่า anotherCall() จะถูกเรียกก็ต่อเมื่อ someCall() เสร็จสมบูรณ์แล้ว? วิธีที่สวยที่สุดในการเรียกพวกมันพร้อมกันคืออะไร?

ฉันอยากใช้มันใน Node ดังนั้นบางทีอาจมีวิธีแก้ด้วยไลบรารี async?

แก้ไข: ฉันไม่พอใจกับวิธีแก้ปัญหาที่ให้ไว้ในคำถามนี้: การชะลอตัวเนื่องจากการรอแบบไม่ขนานของ promises ใน async generators เพราะมันใช้ generators และฉันกำลังถามเกี่ยวกับกรณีการใช้งานที่ทั่วไปมากกว่า

คำตอบ #1

คุณสามารถรอผลจาก Promise.all() ได้:

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

เพื่อเก็บผลลัพธ์:

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

โปรดทราบว่า Promise.all ล้มเหลวอย่างรวดเร็ว ซึ่งหมายความว่าเมื่อมีคำสัญญาหนึ่งที่ส่งให้มันถูกปฏิเสธ ทันทีที่นั้น สิ่งทั้งหมดก็จะถูกปฏิเสธตาม

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'

หากคุณต้องการรอให้พรอมิสทั้งหมดถูกทำให้สำเร็จหรือถูกปฏิเสธ คุณสามารถใช้ Promise.allSettled โปรดทราบว่า Internet Explorer ไม่รองรับวิธีนี้โดยตรง

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

หมายเหตุ: หากคุณใช้ Promise.all การกระทำที่สามารถเสร็จก่อนที่จะเกิดการปฏิเสธจะไม่ถูกย้อนกลับ ดังนั้นคุณอาจต้องดูแลสถานการณ์ดังกล่าว ตัวอย่างเช่น หากคุณมี 5 การกระทำ 4 การทำงานเร็ว 1 การทำงานช้า และการทำงานช้านั้นถูกปฏิเสธ การกระทำทั้ง 4 อาจจะถูกดำเนินการแล้ว ดังนั้นคุณอาจต้องย้อนกลับ ในสถานการณ์ดังกล่าวควรพิจารณาใช้ Promise.allSettled เพราะมันจะให้รายละเอียดที่ชัดเจนว่าการกระทำใดล้มเหลวและการกระทำใดไม่ล้มเหลว

คำตอบ #2

สรุปย่อ

ใช้ Promise.all สำหรับการเรียกฟังก์ชันแบบขนาน คำตอบจะทำงานไม่ถูกต้องเมื่อเกิดข้อผิดพลาด

ก่อนอื่น ให้เรียกใช้ ทุก การเรียกใช้งานแบบอะซิงโครนัสทั้งหมดพร้อมกันและรับวัตถุ Promise ทั้งหมด จากนั้น ใช้ await กับวัตถุ Promise เหล่านั้น ด้วยวิธีนี้ ในขณะที่คุณรอให้ Promise ตัวแรกเสร็จสิ้น การเรียกใช้งานอะซิงโครนัสอื่น ๆ ก็ยังคงดำเนินอยู่ โดยรวมแล้ว คุณจะรอเพียงเท่ากับการเรียกใช้งานอะซิงโครนัสที่ช้าที่สุด ตัวอย่างเช่น:

// 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: http://jsbin.com/xerifanima/edit?js,console

คำเตือน: ไม่สำคัญว่าการเรียกใช้ await จะอยู่ในบรรทัดเดียวกันหรือหลายบรรทัด ตราบใดที่การเรียกใช้ await ครั้งแรกเกิดขึ้น หลังจาก การเรียกใช้แบบอะซิงโครนัสทั้งหมด ดูความคิดเห็นของ JohnnyHK

อัปเดต: คำตอบนี้มีช่วงเวลาที่ต่างออกไปในการจัดการข้อผิดพลาดตามคำตอบของ @bergi, ซึ่ง ไม่ได้ โยนข้อผิดพลาดออกเมื่อเกิดข้อผิดพลาด แต่จะเกิดขึ้นหลังจากที่ Promise ทั้งหมดถูกเรียกใช้งานแล้ว ผมเปรียบเทียบผลลัพธ์กับคำแนะนำของ @jonny: [result1, result2]=Promise.all([async1(), async2()]), ตรวจสอบโค้ดตัวอย่างด้านล่าง

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

คำตอบ #3

อัปเดต:

คำตอบต้นฉบับทำให้ยาก (และในบางกรณีเป็นไปไม่ได้) ที่จะจัดการกับการปฏิเสธ promise อย่างถูกต้อง วิธีแก้ปัญหาที่ถูกต้องคือการใช้ Promise.all:

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

คำตอบต้นฉบับ:

เพียงแค่แน่ใจว่าคุณเรียกใช้ฟังก์ชันทั้งสองก่อนที่คุณจะรอผลของฟังก์ชันใดฟังก์ชันหนึ่ง:

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

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

คำตอบ #4

มีวิธีอีกวิธีหนึ่งโดยไม่ใช้ Promise.all() เพื่อทำงานแบบขนาน:

ก่อนอื่น เรามีฟังก์ชัน 2 ฟังก์ชันสำหรับพิมพ์ตัวเลข:

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

นี่เป็นลำดับ

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

นี่เป็นแบบขนาน:

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

คำตอบ #5

ในกรณีของฉัน ฉันมีหลายงานที่ต้องการดำเนินการพร้อมกัน แต่ฉันจำเป็นต้องทำบางอย่างที่แตกต่างออกไปกับผลลัพธ์ของงานเหล่านั้น

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

และผลลัพธ์:

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

คำตอบ #6

ฉันได้สร้าง gist เพื่อทดสอบวิธีการต่าง ๆ ในการจัดการ promises พร้อมผลลัพธ์ มันอาจเป็นประโยชน์ในการดูตัวเลือกที่ได้ผล

แก้ไข: เนื้อหา Gist ตามความคิดเห็นของ 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 }
*/

บทความที่เกี่ยวข้อง