2012-07-14 57 views
61

Tôi đang chạy một vòng lặp sự kiện có dạng sau:Asynchronous Process bên trong một vòng lặp for javascript

var i; 
var j = 10; 
for (i = 0; i < j; i++) { 

    asycronouseProcess(callBackFunction() { 
     alert(i); 
    }); 
} 

Những gì tôi muốn này để hiển thị là một loạt các cảnh báo cho thấy số từ 0 đến 10. Vấn đề là khi chức năng gọi lại được kích hoạt, vòng lặp đã trải qua một vài lần lặp và nó hiển thị giá trị cao hơn của i. Bất kỳ đề xuất về cách sửa lỗi này?

+0

Cách thêm tham số i vào hàm 'asynchronousProcess'? Mà có thể vượt qua nó trên để callbackFunction –

+1

có thể trùng lặp của [Javascript đóng cửa bên trong vòng - ví dụ đơn giản thực tế] (http://stackoverflow.com/questions/750486/javascript-closure-inside-loops-simple-practical-example) –

Trả lời

114

Vòng lặp for chạy ngay lập tức để hoàn tất trong khi tất cả các hoạt động không đồng bộ của bạn được bắt đầu. Khi họ hoàn thành một số thời gian trong tương lai và gọi callbacks của họ, giá trị của biến chỉ số vòng lặp của bạn i sẽ có giá trị cuối cùng cho tất cả các cuộc gọi lại.

Điều này là do vòng lặp for không chờ một thao tác không đồng bộ hoàn thành trước khi tiếp tục lặp lại tiếp theo của vòng lặp và do cuộc gọi lại không đồng bộ được gọi là thời gian trong tương lai. Do đó, vòng lặp hoàn thành các lần lặp của nó và THEN các callback được gọi khi các hoạt động async đó kết thúc. Như vậy, chỉ số vòng lặp là "thực hiện" và ngồi ở giá trị cuối cùng của nó cho tất cả các cuộc gọi lại.

Để giải quyết vấn đề này, bạn phải lưu duy nhất chỉ mục vòng lặp riêng cho từng cuộc gọi lại. Trong Javascript, cách để làm điều đó là bắt giữ nó trong một hàm đóng. Điều đó có thể được thực hiện bằng cách tạo một hàm đóng nội dòng đặc biệt cho mục đích này (ví dụ đầu tiên được hiển thị bên dưới) hoặc bạn có thể tạo một hàm bên ngoài mà bạn chuyển chỉ mục đến và duy trì chỉ mục duy nhất cho bạn (ví dụ thứ hai được hiển thị bên dưới).

Tính đến năm 2016, nếu bạn có một để-spec lên đầy đủ ES6 thi hành Javascript, bạn cũng có thể sử dụng let để xác định các biến for vòng lặp và nó sẽ được xác định duy nhất cho mỗi lần lặp của for vòng lặp (thi thứ ba phía dưới). Nhưng, lưu ý đây là một tính năng triển khai muộn trong việc triển khai ES6, do đó bạn phải đảm bảo rằng môi trường thực thi của bạn hỗ trợ tùy chọn đó.

Sử dụng.foreach() để lặp vì nó tạo ra chức năng đóng cửa riêng của mình

someArray.forEach(function(item, i) { 
    asynchronousProcess(function(item) { 
     console.log(i); 
    }); 
}); 

Tạo riêng cho chức năng Đóng cửa của bạn sử dụng một IIFE

var j = 10; 
for (var i = 0; i < j; i++) { 
    (function(cntr) { 
     // here the value of i was passed into as the argument cntr 
     // and will be captured in this function closure so each 
     // iteration of the loop can have it's own value 
     asynchronousProcess(function() { 
      console.log(cntr); 
     }); 
    })(i); 
} 

Tạo hoặc Modify Chức năng bên ngoài và Vượt qua nó Biến

Nếu bạn có thể sửa đổi chức năng asynchronousProcess(), thì bạn chỉ có thể chuyển giá trị trong đó và có asynchronousProcess() hoạt các CNTR lại gọi lại như thế này:

var j = 10; 
for (var i = 0; i < j; i++) { 
    asynchronousProcess(i, function(cntr) { 
     console.log(cntr); 
    }); 
} 

Sử dụng ES6 let

Nếu bạn có một môi trường thực thi Javascript hỗ trợ đầy đủ ES6, bạn có thể sử dụng let trong vòng lặp for của bạn như thế này:

const j = 10; 
for (let i = 0; i < j; i++) { 
    asynchronousProcess(function() { 
     console.log(i); 
    }); 
} 

let khai báo trong một tuyên bố for vòng lặp như thế này sẽ tạo ra một uni giá trị que của i cho mỗi lần gọi vòng lặp (đó là những gì bạn muốn).

Serializing với lời hứa và async/chờ đợi

Nếu chức năng async của bạn trả về một lời hứa, và bạn muốn serialize hoạt động async của bạn để chạy cái khác thay vì song song và bạn đang chạy trong một hiện đại môi trường hỗ trợ asyncawait, sau đó bạn có nhiều tùy chọn hơn.

async function someFunction() { 
    const j = 10; 
    for (let i = 0; i < j; i++) { 
     // wait for the promise to resolve before advancing the for loop 
     await asynchronousProcess(); 
     console.log(i); 
    } 
} 

này sẽ đảm bảo rằng chỉ có một cuộc gọi đến asynchronousProcess() đang bay tại một thời điểm và for loop thậm chí sẽ không thúc đẩy cho đến khi mỗi người được thực hiện. Điều này khác với các lược đồ trước đó mà tất cả chạy các hoạt động không đồng bộ của bạn song song nên nó phụ thuộc hoàn toàn vào thiết kế mà bạn muốn. Lưu ý: await hoạt động với lời hứa để chức năng của bạn phải trả lại lời hứa được giải quyết/bị từ chối khi thao tác không đồng bộ hoàn tất. Ngoài ra, lưu ý rằng để sử dụng await, hàm chứa phải được khai báo async.

+1

Thêm tùy chọn thứ hai nếu bạn có thể sửa đổi hàm 'asycronouseProcess()'. – jfriend00

+0

Tuyệt vời! Điều này đã giúp tôi rất nhiều với CasperJS. – Manu

+0

Đã thêm ES6 'let' implementation. – jfriend00

0

Mã JavaScript chạy trên một chuỗi duy nhất, do đó bạn không thể chủ yếu chặn để đợi lặp đầu tiên lặp lại trước khi bắt đầu tiếp theo mà không ảnh hưởng nghiêm trọng đến khả năng sử dụng của trang.

Giải pháp phụ thuộc vào những gì bạn thực sự cần. Nếu ví dụ này gần với chính xác những gì bạn cần, đề xuất của @ Simon để chuyển i cho quy trình không đồng bộ của bạn là một quy trình tốt.

9

Bất kỳ đề xuất nào về cách sửa lỗi này?

Một số.Bạn có thể sử dụng bind:

for (i = 0; i < j; i++) { 
    asycronouseProcess(function (i) { 
     alert(i); 
    }.bind(null, i)); 
} 

Hoặc, nếu trình duyệt của bạn hỗ trợ let (nó sẽ được trong phiên bản ECMAScript tiếp theo, tuy nhiên Firefox đã hỗ trợ nó kể từ một thời gian), bạn có thể có:

for (i = 0; i < j; i++) { 
    let k = i; 
    asycronouseProcess(function() { 
     alert(k); 
    }); 
} 

Hoặc , bạn có thể thực hiện công việc của bind theo cách thủ công (trong trường hợp trình duyệt không hỗ trợ nó, nhưng tôi có thể nói bạn có thể triển khai shim trong trường hợp đó, nó phải nằm trong liên kết ở trên):

for (i = 0; i < j; i++) { 
    asycronouseProcess(function(i) { 
     return function() { 
      alert(i) 
     } 
    }(i)); 
} 

Tôi thường thích let khi tôi có thể sử dụng nó (ví dụ: cho Firefox add-on); nếu không bind hoặc một hàm currying tùy chỉnh (không cần đối tượng ngữ cảnh).

+0

Ví dụ ECMAScript là một ví dụ rất tốt để chứng minh những gì 'let' có thể làm. – hazelnut

+0

Có phải «asyncronouseProcess' trong tất cả các câu trả lời của một số loại trình giữ chỗ không? Tôi đang nhận được "không được xác định". – JackHasaKeyboard

+0

'asyncronouseProcess' là một phần của câu hỏi ban đầu, do đó, nó là bình thường nếu nó mang lại cho bạn" không được xác định ". Bạn chỉ có thể thay thế nó bằng bất kỳ chức năng async nào nếu bạn muốn kiểm tra vấn đề ban đầu và cách giải pháp được đề xuất hoạt động. Ví dụ: 'function asycronouseProcess (fn) {setTimeout (fn, 100);}' – ZER0

5

async await ở đây (ES7), vì vậy bạn có thể thực hiện những việc này một cách dễ dàng ngay bây giờ.

var i; 
    var j = 10; 
    for (i = 0; i < j; i++) { 
    await asycronouseProcess(); 
    alert(i); 
    } 

Hãy nhớ rằng, việc này chỉ khi asycronouseProcess đang trở lại một Promise

Nếu asycronouseProcess không nằm trong kiểm soát của bạn thì bạn có thể làm cho nó trở lại một Promise một mình như thế này

function asyncProcess() { 
    return new Promise((resolve, reject) => { 
    asycronouseProcess(()=>{ 
     resolve(); 
    }) 
    }) 
} 

Sau đó, thay thế dòng này await asycronouseProcess(); theo await asyncProcess();

Hiểu Promises trước khi thậm chí nhìn vào async await là phải (Cũng đọc về hỗ trợ cho async await)

0

var i = 0; 
 
var length = 10; 
 

 
function for1() { 
 
    console.log(i); 
 
    for2(); 
 
} 
 

 
function for2() { 
 
    if (i == length) { 
 
    return false; 
 
    } 
 
    setTimeout(function() { 
 
    i++; 
 
    for1(); 
 
    }, 500); 
 
} 
 
for1();

Đây là một cách tiếp cận chức năng mẫu với những gì được mong đợi ở đây.

Các vấn đề liên quan