2009-04-03 39 views
53

Tôi đang sử dụng Javascript để phân tích cú pháp một tệp XML với khoảng 3.500 phần tử. Tôi đang sử dụng một jQuery "mỗi" chức năng, nhưng tôi có thể sử dụng bất kỳ hình thức vòng lặp.
Vấn đề là trình duyệt bị treo trong vài giây trong khi vòng lặp thực hiện. Cách tốt nhất để ngừng đóng băng trình duyệt mà không làm chậm mã quá nhiều là gì?Làm thế nào để dừng vòng lặp Javascript dữ dội khỏi việc đóng băng trình duyệt

$(xmlDoc).find("Object").each(function() { 
    //Processing here 
}); 
+4

Nhận một ngôn ngữ nhanh hơn! Không, thực sự: trừ khi nó hoàn toàn cần thiết, không sử dụng JS cho điều này - như bạn thấy, nó là 1) đơn luồng và 2) chậm. – Piskvor

+5

Đây là một chức năng phía máy khách, và JS là cần thiết. –

+11

@Triptych - Và các lựa chọn của anh ấy là gì? Chắc chắn người ta hy vọng rằng việc nâng hạng nặng như thế này có thể được thực hiện ở phía máy chủ, nhưng vì chúng ta không biết tình hình của anh ta tốt nhất nên cho rằng anh ta có lý do chính đáng để thực hiện nó, và khi làm việc với ứng dụng web , bạn chỉ thực sự có một sự lựa chọn giữa Javascript và, cũng ... Javascript. – Toji

Trả lời

67

tôi sẽ mương "mỗi" chức năng trong lợi của một vòng lặp for vì nó là nhanh hơn. Tôi cũng sẽ thêm một số chờ đợi bằng cách sử dụng "setTimeout" nhưng chỉ mỗi lần như vậy thường xuyên và chỉ khi cần thiết. Bạn không muốn chờ 5ms mỗi lần vì sau đó xử lý 3500 bản ghi sẽ mất khoảng 17,5 giây.

Dưới đây là một ví dụ sử dụng vòng lặp for xử lý 100 bản ghi (bạn có thể tinh chỉnh điều đó) ở khoảng thời gian 5 mili giây, cho phép chi phí 175 ms.

var xmlElements = $(xmlDoc).find('Object'); 
var length = xmlElements.length; 
var index = 0; 
var process = function() { 
    for (; index < length; index++) { 
    var toProcess = xmlElements[index]; 
    // Perform xml processing 
    if (index + 1 < length && index % 100 == 0) { 
     setTimeout(process, 5); 
    } 
    } 
}; 
process(); 

Tôi cũng sẽ đánh giá các phần khác nhau của quá trình xử lý xml để xem liệu có một nút cổ chai ở đâu đó có thể được khắc phục hay không. Bạn có thể chuẩn trong firefox bằng cách sử dụng trình tạo hồ sơ của firebug và bằng cách viết ra bảng điều khiển như sau:

// start benchmark 
var t = new Date(); 
// some xml processing 
console.log("Time to process: " + new Date() - t + "ms"); 

Hy vọng điều này sẽ hữu ích.

+5

Đây là một ý tưởng tuyệt vời - sử dụng định kỳ setTimeout. Nó hoạt động với thời gian chờ là 0. –

+0

Tôi đã thực hiện chính xác điều này đối với một số ứng dụng web yêu cầu xử lý dữ liệu khổng lồ ở cuối ứng dụng khách. Làm việc như một sự quyến rũ, ngay cả khi nó đòi hỏi một chút cơ cấu lại. – Toji

+8

Mã mát. Có lẽ tôi đang thiếu một cái gì đó, nhưng tôi đã phải thêm một 'index ++' và 'break' sau setTimeout() để làm việc này. –

22

Đặt thời gianOut giữa chế biến để ngăn vòng lặp ngừng ăn hết tài nguyên trình duyệt. Tổng cộng nó sẽ chỉ mất vài giây để xử lý và lặp qua tất cả mọi thứ, không phải không hợp lý cho 3.500 yếu tố.

var xmlElements = $(xmlDoc).find('Object'); 

var processing = function() { 
    var element = xmlElements.shift(); 

    //process element; 

    if (xmlElements.length > 0) { 
    setTimeout(processing, 5); 
    } 
} 

processing(); 
+1

thậm chí có thể hoạt động với thời gian chờ là 0 – Christoph

+0

Tôi đã quyết định phương pháp này, ngoại trừ tôi chỉ chạy setTimeout mỗi 50 phần tử. Và có, nó hoạt động với thời gian chờ là 0. –

+0

cảm ơn rất nhiều vì mã đó –

2

Javascript là đơn luồng, do đó, ngoài setTimeout, bạn không thể làm gì nhiều. Nếu sử dụng Google Gears là một tùy chọn cho trang web của bạn, chúng cung cấp khả năng chạy javascript trong một chuỗi nền thực sự.

6

Tôi muốn cân nhắc việc chuyển đổi 3500 phần tử từ xml sang JSON serverside hoặc thậm chí tải nó lên máy chủ được chuyển đổi tốt hơn, để nó có nguồn gốc từ JS từ getgo.

Điều này sẽ giảm thiểu tải của bạn và làm cho kích thước tệp cũng nhỏ hơn.

1

Bạn có thể sử dụng API công nhân HTML5, nhưng điều đó sẽ chỉ hoạt động trên Firefox 3.1 và Safari 4 beta.

+0

Tôi đồng ý với API công nhân. – FidEliO

+5

Nhưng bạn không thể thực hiện thao tác DOM từ API công nhân –

2

bạn có thể setTimeout() với thời gian ZERO và nó sẽ mang lại như mong muốn

3

Vòng lặp dài mà không đóng băng trình duyệt là có thể với khung công tác Turboid. Với nó, bạn có thể viết mã như:

loop(function(){ 
     // Do something... 
}, number_of_iterations, number_of_milliseconds); 

Xem thêm chi tiết trong bài viết này turboid.net: Real loops in Javascript

1

tôi đã cùng một vấn đề mà đã xảy ra khi người dùng làm mới trang liên tiếp. Lý do là hai lồng nhau cho các vòng đã xảy ra hơn 52000 lần. Vấn đề này khắc nghiệt hơn trong Firefox 24 so với Chrome 29 vì Firefox sẽ sớm bị lỗi hơn (khoảng 2000 m so với Chrome). Những gì tôi đơn giản làm và nó hoạt động là tôi sử dụng "cho" vòng thay vì mỗi lần và sau đó tôi đã tái cấu trúc mã để tôi chia mảng toàn bộ thành 4 cuộc gọi riêng biệt và sau đó kết hợp kết quả thành một. Giải pháp này đã chứng minh rằng nó đã làm việc.

Something như thế này:

var entittiesToLoop = ["..."]; // Mainly a big array 
    loopForSubset(0, firstInterval); 
    loopForSubset(firstInterval, secondInterval); 
    ... 

var loopForSubset = function (startIndex, endIndex) { 
    for (var i=startIndex; i < endIndex; i++) { 
      //Do your stuff as usual here 
    } 
} 

Các giải pháp khác cũng làm việc cho tôi là cùng một giải pháp thực hiện với Worker APIs từ HTML5. Sử dụng cùng một khái niệm trong công nhân khi họ tránh trình duyệt của bạn bị đóng băng vì chúng chạy trong nền của chuỗi chính của bạn. Nếu chỉ áp dụng điều này với API công nhân không hoạt động, hãy đặt từng trường hợp loopForSubset vào các công nhân khác nhau và hợp nhất kết quả bên trong người gọi chính của Người lao động.

Tôi có nghĩa là điều này có thể không hoàn hảo nhưng điều này đã hiệu quả. Tôi có thể giúp với nhiều khối mã thực hơn, nếu ai đó vẫn nghĩ rằng điều này có thể giúp họ.

1

Bạn có thể thử rút ngắn mã bằng cách

$(xmlDoc).find("Object").each(function(arg1) { 
    (function(arg1_received) { 
       setTimeout(function(arg1_received_reached) { 

        //your stuff with the arg1_received_reached goes here 

       }(arg1_received), 0) 
      })(arg1) 
}(this)); 

này sẽ không làm hại bạn nhiều;)

0

Là một sửa đổi của @ tj111 trả lời có thể sử dụng đầy đủ đang

//add pop and shift functions to jQuery library. put in somewhere in your code. 
    //pop function is now used here but you can use it in other parts of your code. 
    (function($) { 
     $.fn.pop = function() { 
      var top = this.get(-1); 
      this.splice(this.length-1,1); 
      return top; 
     }; 

     $.fn.shift = function() { 
      var bottom = this.get(0); 
      this.splice(0,1); 
      return bottom; 
     }; 
    })(jQuery); 


//the core of the code: 
    var $div = $('body').find('div');//.each(); 
    var s= $div.length; 
    var mIndex = 0; 
    var process = function() { 
     var $div = $div.first();    
    //here your own code. 

    //progress bar: 
     mIndex++; 
    // e.g.: progressBar(mIndex/s*100.,$pb0); 

    //start new iteration. 
     $div.shift(); 
     if($div.size()>0){ 
      setTimeout(process, 5); 
     } else { 
    //when calculations are finished. 
      console.log('finished'); 
     } 
    } 
    process(); 
Các vấn đề liên quan