2014-10-21 20 views
5

Tôi đã gặp phải một hành vi không mong muốn của JS setTimeout khi các cửa sổ hộp thoại kiểu như alert mở và tôi muốn biết lý do đằng sau nó.Tại sao các hộp tin nhắn phương thức JS tạm dừng đếm ngược trên setTimeout()?

Tôi dự kiến ​​setTimeout (fn, 10000) có nghĩa là "kiểm tra thời gian hiện tại định kỳ và khi nó lớn hơn Now + 10000ms kích hoạt trình xử lý sự kiện sẽ gọi hàm 'fn' được truyền qua". Điều này sẽ là hợp lý, nhìn thấy cách chúng tôi vượt qua các biện pháp thời gian chờ là 'ms từ bây giờ'. Nhưng, rõ ràng, đếm ngược trên setTimeout là đếm ngược theo nghĩa đen và sẽ bị tạm dừng trong khi cửa sổ phương thức mở.

setTimeout(function(){ 
    //alert A 
    alert("10 seconds have passed for the first setTimeout") 
}, 10000); 
setTimeout(function(){ 
    //alert B 
    alert("Wait for 15 seconds and press OK"); 
},1000); 

Tôi mong chờ cảnh báo A để hiển thị ngay lập tức sau khi bạn đóng cảnh báo B (giả sử bạn chờ 15 giây. Làm như vậy), kể từ khi cảnh báo Đã hết thời gian chỉ trong vòng 10 giây và họ đã trôi qua. Thực tế, tuy nhiên, cho thấy đếm ngược để cảnh báo A chỉ đơn giản là tạm dừng trong khi cảnh báo B được mở và nó sẽ chỉ hiển thị sau khoảng. 9 giây nữa đã trôi qua sau khi bạn đã đóng cảnh báo B, cho dù B đã mở bao lâu.

Điều này có vẻ không hợp lý.

Cập nhật. Tôi chắc chắn không phải là người duy nhất bị nhầm lẫn ở đây, vì hành vi tạm dừng thời gian chờ này xảy ra trong Chrome và Internet Explorer, nhưng không phải Firefox. Firefox thực hiện hành vi mà tôi mong đợi - nếu bạn đợi 15 giây trên cảnh báo B - cảnh báo A xuất hiện tức thì bất cứ khi nào bạn đóng nó.

+0

Đây là một trong những lý do tại sao 'alert's không tốt đặc biệt là với các chức năng thời gian. – soktinpk

+0

@soktinpk Chúng tôi sử dụng những thứ như Bootbox cho cảnh báo trong các dự án của chúng tôi, nhưng đôi khi nó không được áp dụng, như khi bạn cần cảnh báo người dùng về dữ liệu chưa được lưu khi đóng trang. Tôi không thể tưởng tượng mọi người gặp rắc rối vì hành vi này quá thường xuyên, nhưng vẫn còn, một sự khác biệt lớn về hành vi giữa ba trình duyệt lớn là thú vị. –

+0

Cảnh báo người dùng về dữ liệu chưa được lưu là cách sử dụng hoàn toàn hợp lệ cho 'alert' và/hoặc' confirm'. Tuy nhiên, tại sao bạn lại muốn chờ một chút trước khi cảnh báo 'alert' xuất hiện? – soktinpk

Trả lời

5

tôi nghi ngờ có một câu trả lời dứt khoát về lý do tại sao cả IE và Chrome đặt một tạm dừng trên cấp phát bộ hẹn giờ cho đến khi alert đã bị loại bỏ và Firefox thì không. Tôi tin rằng nó chỉ là bởi vì có sự tự do nhất định trong việc giải thích các W3C's specs for alert:

Cảnh báo (message) phương pháp, khi gọi, phải chạy theo các bước sau:

  1. Nếu mức độ làm tổ chấm dứt vòng lặp sự kiện không khác, tùy chọn hủy các bước này.

  2. Phát hành mutex lưu trữ.

  3. Hiển thị thông báo đã cho cho người dùng.

  4. Tùy chọn, tạm dừng trong khi chờ người dùng xác nhận thông báo .

Bước 4 (các tạm dừng) được giải thích thêm here:

Một số thuật toán trong đặc tả này, vì lý do lịch sử, đòi hỏi user agent để tạm dừng trong khi chạy một nhiệm vụ cho đến khi đáp ứng điều kiện mục tiêu. Điều này có nghĩa chạy theo các bước sau:

  1. Nếu bất kỳ thuật toán đồng bộ chạy đang chờ một trạng thái ổn định, sau đó chạy phần đồng bộ của họ và sau đó tiếp tục chạy thuật toán đồng bộ của họ. (Xem event loop xử lý định nghĩa mô hình trên để biết chi tiết.)

  2. Nếu cần thiết, cập nhật Cung cấp hay giao diện người dùng của bất kỳ bối cảnh Document hoặc duyệt qua để phản ánh tình trạng hiện tại.

  3. Đợi đến khi đạt được mục tiêu điều kiện. Trong khi tác nhân người dùng có tác vụ bị tạm dừng , vòng lặp sự kiện tương ứng không được chạy thêm tác vụ và bất kỳ tập lệnh nào trong tác vụ hiện đang chạy đều phải chặn. Các tác nhân người dùng nên vẫn đáp ứng với đầu vào của người dùng trong khi bị tạm dừng, tuy nhiên, mặc dù trong khả năng giảm do vòng lặp sự kiện sẽ không hoạt động.

Vì vậy, vòng lặp sự kiện bị tạm ngưng trong mọi trường hợp. Cuộc gọi lại của thời gian chờ lâu hơn không được gọi trong khi cảnh báo vẫn hiển thị và phương thức. Nếu nó không theo cách này, tất cả các loại nasties có thể được thực hiện có thể, giống như nhiều cảnh báo trên đầu trang của mỗi khác.

Bây giờ, bạn có thể nói từ các thông số kỹ thuật ở trên nếu tính giờ đếm ngược sẽ bị tạm ngưng trong suốt thời gian của cảnh báo hay không nên được kích hoạt ngay khi cảnh báo đã hết? Tôi không thể, và tôi thậm chí không chắc chắn hành vi nào sẽ hợp lý hơn.

Điều tôi chắc chắn là bạn không nên sử dụng cảnh báo JavaScript cho bất kỳ mục đích nào khác ngoài mục đích gỡ lỗi. Cảnh báo cho phép tạm ngưng thực thi tập lệnh (trong khi một số thao tác không đồng bộ như XHR đang diễn ra trong nền), nhưng chúng khá không thân thiện với người dùng. Cách tiếp cận đúng sẽ bao gồm mã không đồng bộ, sử dụng lời hứa và, có thể, ES6 generators/yeild (nếu bạn đang theo kiểu mã tuyến tính).

Các câu dưới đây rất có liên quan và một số giải pháp thay thế để alert sẽ được thảo luận ở đó:

-1

cảnh báo là chặn giao diện người dùng và vì Javascript là một chuỗi duy nhất, nó sẽ chặn mọi thứ chạy cho đến khi hộp thoại bị loại bỏ.

+0

Bạn muốn nói 'cảnh báo là đồng bộ'. Tôi biết điều đó, nhưng điều đó không trả lời câu hỏi. Kỳ vọng ban đầu của tôi là cảnh báo A setTimeout để kiểm tra thời gian hiện tại ngay sau khi chức năng trong cảnh báo B trả về và chuỗi JS được bỏ chặn, tại thời điểm đó sẽ thấy rằng hơn 10000 ms đã trôi qua và thực hiện cảnh báo A. –

-1

Nếu bạn thực sự cần, bạn có thể sử dụng bộ hẹn giờ của riêng mình.

var now = Date.now(); 
function alertA(){ 
    //alert A 
    alert("10 seconds have passed for the first setTimeout") 
} 
setTimeout(function(){ 
    //alert B 
    alert("Wait for 15 seconds and press OK"); 
    setTimeout(alertA, 10000 - (Date.now() - now)); // Subtract the time passed 
},1000); 

Bạn có thể bọc này trong một phương pháp hữu ích:

function alertDelay(messages, timePassed) { 
    if (typeof messages !== "object" || messages.length === 0) return; 
    if (timePassed == null) timePassed = 0; 
    var firstMessage = messages[0]; 
    var now = Date.now(); 
    setTimeout(function() { 
    alert(firstMessage.message); 
    alertDelay(messages.slice(1), Date.now() - now); 
    }, firstMessage.delay - timePassed); 
} 

Cách sử dụng:

alertDelay([ 
    {message: 'Message 1', delay: 1000}, 
    {message: 'Message 2', delay: 5000}, 
    {message: 'Message 3', delay: 10000} 
]); 
+1

Tôi không tìm kiếm cho một workaround được nêu ra, tôi chỉ tò mò là tại sao. Mã này chắc chắn sẽ không giúp được gì nhiều, vì setTimeouts được đặt ở các vị trí khác nhau. Cách duy nhất có thể thực hiện được mà tôi có thể nghĩ ra ở đỉnh đầu là quá tải window.setTimeout để sử dụng setInterval nhanh nhất có thể bên trong, duy trì một danh sách các cuộc gọi lại với ngày đến hạn và kiểm tra chúng so với Date.now(). Nên làm việc, nhưng tôi không nghĩ có một dự án thực sự cần độ chính xác như vậy. Dù sao, như tôi đã nói, quan tâm nhiều hơn đến 'tại sao?' sau đó 'cách khắc phục?'. –

+0

@IvanKoshelew Tôi nghĩ rằng [câu trả lời của dnharjani] (http://stackoverflow.com/a/26487531/3371119) thực hiện một công việc tốt để giải thích tại sao: 'alert' có thể chặn luồng. Nó thực sự phụ thuộc vào việc triển khai 'alert'. Bạn không thể dựa vào nó khi sử dụng chức năng thời gian. – soktinpk

+0

Tôi không nghĩ rằng câu trả lời cho câu hỏi ban đầu. Tôi biết lý do tại sao nó ngăn chặn setTimeout gọi lại trong khi nó được mở - không có bất ngờ ở đó. Những gì tôi không hiểu là lý do tại sao nó tiếp tục chờ đợi thêm sau khi nó được đóng lại. Tại sao chỉ FireFox thực hiện theo cách tôi mong đợi? Tôi có nên gửi điều này dưới dạng lỗi cho các trình gỡ lỗi trình duyệt khác không? Ý tôi là, vì hành vi khác với cùng một mã - một trong số chúng phải sai. Câu hỏi là, cái nào và tại sao? –

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