Cách trả lại nhiều Lời hứa và đợi tất cả chúng trước khi làm những việc khác


85

Tôi có một vòng lặp gọi một phương thức không đồng bộ. Vòng lặp này có thể gọi phương thức nhiều lần. Sau vòng lặp này, tôi có một vòng lặp khác chỉ cần được thực thi khi tất cả những thứ không đồng bộ được thực hiện xong.

Vì vậy, điều này minh họa những gì tôi muốn:

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

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

Tôi không rành về những lời hứa, vậy có ai có thể giúp tôi đạt được điều này không?

Đây là cách doSomeAsyncStuff()cư xử của tôi :

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

Có lẽ tôi phải làm điều gì đó như thế này:

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

Nhưng tôi không chắc về cú pháp.


Bạn có đang kiểm soát các cuộc gọi không đồng bộ không? Họ đã hứa rồi, hay bạn có thể bắt họ trả lại lời hứa không?
TJ Crowder

Trình tự chính xác là gì? Bạn có cần gọi các hàm khác sau khi tất cả các hàm không đồng bộ trước đó đã hoàn tất không? Hay bạn chỉ cần gọi một hàm sau khi mỗi lần không đồng bộ kết thúc?
Sosdoc

Hiện tại, hàm đầu tiên không trả về các lời hứa. Điều đó tôi phải thực hiện. Tôi muốn chỉnh sửa tin nhắn của mình để thêm một số chi tiết về quy trình làm việc của các chức năng của tôi. Và có, tôi cần rằng tất cả nội dung của vòng lặp đầu tiên phải hoàn thành trước khi bắt đầu thực thi nội dung trong vòng lặp thứ hai.
Ganbin

1
Re your edit: "Có lẽ tôi phải làm một cái gì đó như vậy" Yup, rất giống như vậy, ngoại trừ không có sở cuối Promise.
TJ Crowder

Câu trả lời:


161

Bạn có thể sử dụng Promise.all( spec , MDN ) cho điều đó: Nó chấp nhận một loạt các lời hứa riêng lẻ và trả lại cho bạn một lời hứa duy nhất được giải quyết khi tất cả những lời bạn đưa ra được giải quyết hoặc bị từ chối khi bất kỳ lời hứa nào trong số chúng bị từ chối.

Vì vậy, nếu bạn thực hiện doSomeAsyncStuffmột lời hứa trở lại, thì:

    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 có một bài viết về lời hứa tại đây . Tôi cũng trình bày chi tiết về các promsies trong Chương 8 của cuốn sách JavaScript: Đồ chơi Mới của tôi, các liên kết trong hồ sơ của tôi nếu bạn quan tâm.

Đây là một ví dụ:

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

Đầu ra mẫu (vì Math.random, những gì kết thúc đầu tiên có thể khác nhau):

Đang giải quyết 3
Đang giải quyết 2
Đang giải quyết 1
Đang giải quyết 4
Đang giải quyết 0
Tất cả đã hoàn thành [0,1,2,3,4]

Được rồi, cảm ơn Tôi thử cái này ngay bây giờ và tôi sẽ có phản hồi sau vài phút.
Ganbin

12
Wow, cảm ơn rất nhiều, bây giờ tôi đã hiểu nhiều hơn về các lời hứa. Tôi đã đọc rất nhiều về các lời hứa, nhưng cho đến khi chúng tôi cần sử dụng chúng trong mã thực, chúng tôi vẫn chưa thực sự hiểu tất cả các cơ chế. Bây giờ tôi đã hiểu rõ hơn và tôi có thể bắt đầu viết những thứ hay ho, nhờ bạn.
Ganbin

1
Ngoài ra, nếu bạn muốn hoàn thành các nhiệm vụ này vì bất kỳ lý do gì (ví dụ: chế giễu tiến độ), bạn có thể thay đổi Math.floor(Math.random() * 1000)thành(i * 1000)
OK chắc chắn

@TJ nay làm thế nào tôi có thể làm cho các dữ liệu kết quả đến xem và có tôi có thể làm vòng lặp để hiển thị dữ liệu
Ajit Singh

1
@ user1063287 - Bạn có thể làm điều đó nếu mã nằm trong ngữ cảnh awaitđược phép. Hiện tại, nơi duy nhất bạn có thể sử dụng awaitlà bên trong một asynchàm. (Tại một số điểm bạn cũng sẽ có thể sử dụng nó ở cấp cao nhất của mô-đun.)
TJ Crowder

5

Một chức năng có thể tái sử dụng hoạt động tốt cho mẫu này:

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

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

  return Promise.all(promises);
}

Ví dụ OP:

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

Một mẫu có liên quan, đang lặp lại trên một mảng và thực hiện thao tác không đồng bộ trên từng mục:

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

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

  return Promise.all(promises);
}

Thí dụ:

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
Điều này thực sự làm cho mã dễ hiểu và sạch hơn. Tôi không nghĩ rằng ví dụ hiện tại (rõ ràng đã được điều chỉnh theo mã của OP) thực hiện điều này. Đây là một thủ thuật gọn gàng, cảm ơn!
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)),
]);

1

Đây là mã mà tôi đã viết cho chính mình để hiểu câu trả lời được nêu ở đây. Tôi có các truy vấn mongoose trong vòng lặp for, vì vậy tôi đặt ở đây asyncFunctionđể thay thế nó. Hy vọng nó sẽ giúp bất cứ ai. Bạn có thể chạy tập lệnh này trong nút hoặc bất kỳ thời gian chạy Javascript nào.

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

0

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

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.