วิธีคืนสัญญามากมายและรอให้หมดก่อนทำอย่างอื่น


91

ฉันมีลูปที่เรียกเมธอดที่ทำสิ่งต่างๆแบบอะซิงโครนัส ลูปนี้สามารถเรียกใช้เมธอดได้หลายครั้ง หลังจากลูปนี้ฉันมีลูปอื่นที่ต้องดำเนินการก็ต่อเมื่อทำสิ่งที่ไม่ตรงกันทั้งหมด

สิ่งนี้แสดงให้เห็นถึงสิ่งที่ฉันต้องการ:

for (i = 0; i < 5; i++) {
    doSomeAsyncStuff();    
}

for (i = 0; i < 5; i++) {
    doSomeStuffOnlyWhenTheAsyncStuffIsFinish();    
}

ฉันไม่ค่อยคุ้นเคยกับคำสัญญามีใครช่วยฉันทำสิ่งนี้ให้สำเร็จได้บ้าง

นี่คือdoSomeAsyncStuff()พฤติกรรมของฉัน:

function doSomeAsyncStuff() {
    var editor = generateCKEditor();
    editor.on('instanceReady', function(evt) {
        doSomeStuff();
        // There should be the resolve() of the promises I think.
    })
}

บางทีฉันอาจต้องทำสิ่งนี้:

function doSomeAsyncStuff() {
    var editor = generateCKEditor();
    return new Promise(function(resolve,refuse) {
        editor.on('instanceReady', function(evt) {
            doSomeStuff();
            resolve(true);
        });
    });
}

แต่ฉันไม่แน่ใจเกี่ยวกับไวยากรณ์


คุณเป็นผู้ควบคุมการโทรแบบอะซิงโครนัสหรือไม่? พวกเขาคืนคำสัญญาแล้วหรือคุณสามารถให้สัญญาคืนกลับมาได้?
TJ Crowder

ลำดับคืออะไรกันแน่? คุณจำเป็นต้องเรียกใช้ฟังก์ชั่นอื่น ๆ หลังจากที่ทุกคน async ก่อนหน้านี้จะเสร็จสิ้น? หรือคุณต้องเรียกใช้ฟังก์ชันหลังจาก async แต่ละตัวเสร็จสิ้นแล้ว?
Sosdoc

สำหรับตอนนี้ฟังก์ชันแรกจะไม่คืนสัญญา ที่ต้องนำไปปฏิบัติ ฉันต้องการแก้ไขข้อความเพื่อเพิ่มรายละเอียดขั้นตอนการทำงานของฟังก์ชันของฉัน และใช่ฉันต้องการให้ทุกสิ่งของลูปแรกเสร็จสิ้นก่อนที่จะเริ่มดำเนินการสิ่งต่างๆในลูปที่สอง
Ganbin

1
เรื่องการแก้ไขของคุณ: "บางทีฉันต้องทำอะไรอย่างนั้น" Yup มากเช่นที่ยกเว้นไม่มีในตอนท้ายของs Promise
TJ Crowder

คำตอบ:


169

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

ดังนั้นหากคุณdoSomeAsyncStuffคืนคำสัญญาแล้ว:

    const promises = [];
//  ^^^^^−−−−−−−−−−−−−−−−−−−−−−−−−−− use `const` or `let`, not `var`
    
    for (let i = 0; i < 5; i++) {
//       ^^^−−−−−−−−−−−−−−−−−−−−−−−− added missing declaration
        promises.push(doSomeAsyncStuff());
    }
    
    Promise.all(promises)
        .then(() => {
            for (let i = 0; i < 5; i++) {
//               ^^^−−−−−−−−−−−−−−−− added missing declaration
                doSomeStuffOnlyWhenTheAsyncStuffIsFinish();    
            }
        })
        .catch((e) => {
            // handle errors here
        });

MDN มีบทความเกี่ยวกับสัญญาที่นี่ ฉันยังอธิบายรายละเอียดเกี่ยวกับพรหมลิขิตในบทที่ 8 ของหนังสือJavaScript: The New Toysลิงก์ในโปรไฟล์ของฉันหากคุณสนใจ

นี่คือตัวอย่าง:

 function doSomethingAsync(value) {
     return new Promise((resolve) => {
         setTimeout(() => {
             console.log("Resolving " + value);
             resolve(value);
         }, Math.floor(Math.random() * 1000));
     });
   }
   
   function test() {
       const promises = [];
       
       for (let i = 0; i < 5; ++i) {
           promises.push(doSomethingAsync(i));
       }
       
       Promise.all(promises)
           .then((results) => {
               console.log("All done", results);
           })
           .catch((e) => {
               // Handle errors here
           });
   }
   
   test();

ผลลัพธ์ตัวอย่าง (เนื่องจากMath.randomสิ่งที่เสร็จสิ้นก่อนอาจแตกต่างกันไป):

การแก้ไข 3
การแก้ไข 2
การแก้ไข 1
การแก้ไข 4
การแก้ปัญหา 0
ทำทั้งหมด [0,1,2,3,4]

โอเคขอบคุณฉันลองตอนนี้และได้รับคำติชมในไม่กี่นาที
Ganbin

12
ว้าวขอบคุณมากตอนนี้ฉันเข้าใจคำสัญญามากขึ้นฉันอ่านคำสัญญามากมาย แต่จนกว่าเราจะต้องใช้มันในรหัสจริงเราไม่เข้าใจกลไกทั้งหมดจริงๆ ตอนนี้ฉันดีขึ้นและเริ่มเขียนสิ่งดีๆได้แล้วขอบคุณ
Ganbin

1
นอกจากนี้หากคุณต้องการให้งานเหล่านี้เสร็จสมบูรณ์ตามลำดับไม่ว่าจะด้วยเหตุผลใดก็ตาม (เช่นการเยาะเย้ยความคืบหน้า) คุณสามารถเปลี่ยนMath.floor(Math.random() * 1000)เป็น(i * 1000)
ตกลงให้แน่ใจว่า

ตอนนี้ @TJ ฉันจะแสดงผลข้อมูลเป็นมุมมองได้อย่างไรและที่นั่นฉันสามารถทำวนซ้ำเพื่อแสดงข้อมูลได้
Ajit Singh

1
@ user1063287 - คุณสามารถทำได้หากโค้ดอยู่ในบริบทที่awaitอนุญาต ในขณะนี้สถานที่เดียวที่คุณสามารถใช้ได้awaitคือภายในasyncฟังก์ชัน (ในบางจุดคุณจะสามารถใช้งานได้ที่ระดับบนสุดของโมดูล)
TJ Crowder

6

ฟังก์ชันที่ใช้ซ้ำได้ใช้งานได้ดีสำหรับรูปแบบนี้:

function awaitAll(count, asyncFn) {
  const promises = [];

  for (i = 0; i < count; ++i) {
    promises.push(asyncFn());
  }

  return Promise.all(promises);
}

ตัวอย่าง OP:

awaitAll(5, doSomeAsyncStuff)
  .then(results => console.log('doSomeStuffOnlyWhenTheAsyncStuffIsFinished', results))
  .catch(e => console.error(e));

รูปแบบที่เกี่ยวข้องกำลังวนซ้ำบนอาร์เรย์และดำเนินการ async ในแต่ละรายการ:

function awaitAll(list, asyncFn) {
  const promises = [];

  list.forEach(x => {
    promises.push(asyncFn(x));
  });

  return Promise.all(promises);
}

ตัวอย่าง:

const books = [{ id: 1, name: 'foo' }, { id: 2, name: 'bar' }];

function doSomeAsyncStuffWith(book) {
  return Promise.resolve(book.name);
}

awaitAll(books, doSomeAsyncStuffWith)
  .then(results => console.log('doSomeStuffOnlyWhenTheAsyncStuffIsFinished', results))
  .catch(e => console.error(e));

1
สิ่งนี้ทำให้โค้ดเข้าใจง่ายและสะอาดขึ้นจริงๆ ฉันไม่คิดว่าตัวอย่างปัจจุบัน (ซึ่งเห็นได้ชัดว่าปรับให้เข้ากับรหัสของ OP) จะยุติธรรมขนาดนี้ นี่เป็นเคล็ดลับที่เรียบร้อยขอบคุณ!
Shaun Vermaak

2
const doSomeAsyncStuff = async (funcs) => {
  const allPromises = funcs.map(func => func());
  return await Promise.all(allPromises);
}

doSomeAsyncStuff([
  () => new Promise(resolve => setTimeout(() => resolve(), 100)),
  () => new Promise(resolve => setTimeout(() => resolve(), 100)),
  () => new Promise(resolve => setTimeout(() => resolve(), 100)),
  () => new Promise(resolve => setTimeout(() => resolve(), 100)),
  () => new Promise(resolve => setTimeout(() => resolve(), 100)),
]);

2

นี่คือรหัสที่ฉันเขียนขึ้นเพื่อตัวเองเพื่อให้เข้าใจคำตอบที่ระบุไว้ที่นี่ ฉันมีคำค้นหาพังพอนในการวนซ้ำดังนั้นฉันจึงใช้ที่นี่asyncFunctionแทน หวังว่ามันจะช่วยทุกคน คุณสามารถรันสคริปต์นี้ในโหนดหรือรันไทม์ของ Javascript จำนวนมาก

let asyncFunction = function(value, callback)
{
        setTimeout(function(){console.log(value); callback();}, 1000);
}



// a sample function run without promises

asyncFunction(10,
    function()
    {
        console.log("I'm back 10");
    }
);


//here we use promises

let promisesArray = [];

let p = new Promise(function(resolve)
{
    asyncFunction(20,
        function()
        {
            console.log("I'm back 20");
            resolve(20);
        }
    );
});

promisesArray.push(p);


for(let i = 30; i < 80; i += 10)
{
    let p = new Promise(function(resolve)
    {
        asyncFunction(i,
            function()
            {
                console.log("I'm back " + i);
                resolve(i);
            }
        );
    });
    promisesArray.push(p);
}


// We use Promise.all to execute code after all promises are done.

Promise.all(promisesArray).then(
    function()
    {
        console.log("all promises resolved!");
    }
)

1

/*** Worst way ***/
for(i=0;i<10000;i++){
  let data = await axios.get(
    "https://yourwebsite.com/get_my_data/"
  )
  //do the statements and operations
  //that are dependant on data
}

//Your final statements and operations
//That will be performed when the loop ends

//=> this approach will perform very slow as all the api call
// will happen in series


/*** One of the Best way ***/

const yourAsyncFunction = async (anyParams) => {
  let data = await axios.get(
    "https://yourwebsite.com/get_my_data/"
  )
  //all you statements and operations here
  //that are dependant on data
}
var promises = []
for(i=0;i<10000;i++){
  promises.push(yourAsyncFunction(i))
}
await Promise.all(promises)
//Your final statement / operations
//that will run once the loop ends

//=> this approach will perform very fast as all the api call
// will happen in parallal

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.