คำนำ:
คำตอบอื่น ๆ บางคำตอบถูกต้อง แต่ไม่ได้แสดงให้เห็นจริง ๆ ว่าปัญหาที่แก้แล้วคืออะไรดังนั้นฉันจึงสร้างคำตอบนี้เพื่อนำเสนอภาพประกอบโดยละเอียด
เป็นเช่นนี้ฉันกำลังโพสต์รายละเอียดเดินผ่านของสิ่งที่เบราว์เซอร์ไม่และวิธีการใช้setTimeout()
จะช่วยให้ มันดูค่อนข้างยาว แต่จริงๆแล้วเรียบง่ายและตรงไปตรงมา - ฉันเพิ่งทำรายละเอียดมาก
UPDATE:ฉันได้ทำเพื่อ JSFiddle สดแสดงให้เห็นถึงคำอธิบายดังต่อไปนี้: http://jsfiddle.net/C2YBE/31/ ขอบคุณมากที่ @ThangChung ที่ช่วยกันเริ่มต้นมัน
ปรับปรุง 2:ในกรณีที่เว็บไซต์ JSFiddle ตายหรือลบรหัสฉันเพิ่มรหัสในคำตอบนี้ในตอนท้าย
รายละเอียด :
ลองนึกภาพเว็บแอปด้วยปุ่ม "ทำบางสิ่ง" และ div ผลลัพธ์
onClick
จัดการสำหรับปุ่ม "ทำอะไร" เรียกฟังก์ชั่น "LongCalc ()" ซึ่งจะสิ่งที่ 2:
ทำการคำนวณที่ยาวมาก (บอกว่าใช้เวลา 3 นาที)
พิมพ์ผลลัพธ์ของการคำนวณลงในผลลัพธ์ div
ตอนนี้ผู้ใช้ของคุณเริ่มทดสอบสิ่งนี้คลิกปุ่ม "ทำอะไรบางอย่าง" และหน้าเว็บอยู่ที่นั่นโดยไม่ทำอะไรเลยเป็นเวลา 3 นาทีพวกเขากระสับกระส่ายคลิกปุ่มอีกครั้งรอ 1 นาทีไม่มีอะไรเกิดขึ้นคลิกปุ่มอีกครั้ง ...
ปัญหาชัดเจน - คุณต้องการ "สถานะ" DIV ซึ่งแสดงว่าเกิดอะไรขึ้น เรามาดูกันว่ามันทำงานอย่างไร
ดังนั้นคุณจึงเพิ่ม "สถานะ" DIV (ตอนแรกว่างเปล่า) และแก้ไขonclick
ตัวจัดการ (ฟังก์ชั่นLongCalc()
) เพื่อทำ 4 สิ่ง:
เติมสถานะ "กำลังคำนวณ ... อาจใช้เวลาประมาณ 3 นาที" ในสถานะ DIV
ทำการคำนวณที่ยาวมาก (บอกว่าใช้เวลา 3 นาที)
พิมพ์ผลลัพธ์ของการคำนวณลงในผลลัพธ์ div
เติมสถานะ "การคำนวณเสร็จแล้ว" ลงในสถานะ DIV
และคุณยินดีให้แอปแก่ผู้ใช้ในการทดสอบอีกครั้ง
พวกเขากลับมาหาคุณด้วยความโกรธ และอธิบายว่าเมื่อพวกเขาคลิกปุ่มสถานะ DIV จะไม่อัปเดตด้วยสถานะ "กำลังคำนวณ ... " !!!
คุณเกาหัวถามเกี่ยวกับ StackOverflow (หรืออ่านเอกสารหรือ Google) และตระหนักถึงปัญหา:
เบราว์เซอร์ทุกสถานที่ของ "สิ่งที่ต้องทำ" งาน (งาน UI ทั้งสองและคำสั่งจาวาสคริปต์) เป็นผลมาจากเหตุการณ์ที่เกิดขึ้นเป็นคิวเดียว และน่าเสียดายที่การวาดใหม่ "สถานะ" DIV ด้วยค่า "กำลังคำนวณ ... " ใหม่เป็นสิ่งที่ต้องทำแยกต่างหากซึ่งจะไปยังจุดสิ้นสุดของคิว!
นี่คือรายละเอียดของกิจกรรมในระหว่างการทดสอบของผู้ใช้เนื้อหาของคิวหลังจากแต่ละเหตุการณ์:
- คิว:
[Empty]
- กิจกรรม: คลิกที่ปุ่ม คิวหลังจากเหตุการณ์:
[Execute OnClick handler(lines 1-4)]
- เหตุการณ์: เรียกใช้บรรทัดแรกในตัวจัดการ OnClick (เช่นเปลี่ยนค่าสถานะ DIV)
[Execute OnClick handler(lines 2-4), re-draw Status DIV with new "Calculating" value]
คิวหลังจากเหตุการณ์: โปรดทราบว่าขณะที่การเปลี่ยนแปลง DOM เกิดขึ้นทันทีอีกครั้งวาดองค์ประกอบที่สอดคล้องกัน DOM คุณจำเป็นต้องมีเหตุการณ์ใหม่ที่เกิดจากการเปลี่ยนแปลง DOM, ที่ไปในตอนท้ายของคิว
- ปัญหา!!! ปัญหา!!! รายละเอียดอธิบายด้านล่าง
- เหตุการณ์: เรียกใช้บรรทัดที่สองในตัวจัดการ (การคำนวณ)
[Execute OnClick handler(lines 3-4), re-draw Status DIV with "Calculating" value]
คิวหลัง:
- เหตุการณ์: เรียกใช้บรรทัดที่ 3 ในตัวจัดการ (เติมผลลัพธ์ DIV)
[Execute OnClick handler(line 4), re-draw Status DIV with "Calculating" value, re-draw result DIV with result]
คิวหลัง:
- เหตุการณ์: ดำเนินการบรรทัดที่ 4 ในตัวจัดการ (ระบุสถานะ DIV ด้วย "DONE")
[Execute OnClick handler, re-draw Status DIV with "Calculating" value, re-draw result DIV with result; re-draw Status DIV with "DONE" value]
คิว:
- เหตุการณ์: ดำเนินการโดยนัย
return
จากonclick
ตัวจัดการย่อย เรานำ "Execute OnClick handler" ออกจากคิวและเริ่มดำเนินการรายการถัดไปในคิว
- หมายเหตุ: เนื่องจากเราทำการคำนวณเสร็จแล้ว 3 นาทีจึงส่งให้กับผู้ใช้แล้ว กิจกรรมวาดซ้ำยังไม่เกิดขึ้น !!!
- กิจกรรม: วาดสถานะใหม่ DIV ด้วยค่า "กำลังคำนวณ" เราทำการวาดใหม่และถอดมันออกจากคิว
- เหตุการณ์: วาด DIV ผลลัพธ์อีกครั้งด้วยค่าผลลัพธ์ เราทำการวาดใหม่และถอดมันออกจากคิว
- กิจกรรม: วาดสถานะใหม่ DIV ด้วยค่า "เสร็จสิ้น" เราทำการวาดใหม่และถอดมันออกจากคิว ผู้ชมที่มีตาเฉียบอาจสังเกตเห็น "สถานะ DIV ด้วยค่า" การคำนวณ "ที่กระพริบสำหรับเศษเสี้ยวของไมโครวินาที - หลังจากเสร็จสิ้นการคำนวณ
ดังนั้นปัญหาพื้นฐานคือเหตุการณ์ re-draw สำหรับ "สถานะ" DIV ถูกวางในคิวในตอนท้ายหลังจากเหตุการณ์ "execute line 2" ซึ่งใช้เวลา 3 นาทีดังนั้นการวาดใหม่จริงจะไม่เกิดขึ้นจนกว่า หลังจากการคำนวณเสร็จสิ้น
setTimeout()
เพื่อช่วยเหลือมา มันช่วยได้อย่างไร เพราะด้วยการเรียกใช้โค้ดที่ทำงานแบบยาวผ่านทางsetTimeout
จริง ๆ คุณสร้าง 2 เหตุการณ์: setTimeout
การดำเนินการเองและ (เนื่องจากหมดเวลา 0), รายการคิวแยกต่างหากสำหรับรหัสที่กำลังดำเนินการ
ดังนั้นเพื่อแก้ไขปัญหาของคุณคุณแก้ไขonClick
handler ของคุณให้เป็นคำสั่ง TWO (ในฟังก์ชั่นใหม่หรือเพียงแค่บล็อกภายในonClick
):
เติมสถานะ "กำลังคำนวณ ... อาจใช้เวลาประมาณ 3 นาที" ในสถานะ DIV
ดำเนินการsetTimeout()
ด้วย 0 หมดเวลาและโทรออกไปยังLongCalc()
ฟังก์ชั่น
LongCalc()
ฟังก์ชั่นเกือบเหมือนครั้งที่แล้ว แต่เห็นได้ชัดว่าไม่มีสถานะ "กำลังคำนวณ ... " การอัปเดต DIV เป็นขั้นตอนแรก และเริ่มการคำนวณแทนทันที
ดังนั้นลำดับเหตุการณ์และคิวจะเป็นอย่างไรตอนนี้
- คิว:
[Empty]
- กิจกรรม: คลิกที่ปุ่ม คิวหลังจากเหตุการณ์:
[Execute OnClick handler(status update, setTimeout() call)]
- เหตุการณ์: เรียกใช้บรรทัดแรกในตัวจัดการ OnClick (เช่นเปลี่ยนค่าสถานะ DIV)
[Execute OnClick handler(which is a setTimeout call), re-draw Status DIV with new "Calculating" value]
คิวหลังจากเหตุการณ์:
- เหตุการณ์: เรียกใช้บรรทัดที่สองในตัวจัดการ (การโทร setTimeout)
[re-draw Status DIV with "Calculating" value]
คิวหลัง: คิวไม่มีอะไรใหม่อยู่ในนั้นอีก 0 วินาที
- เหตุการณ์: การเตือนภัยจากการหมดเวลาใช้งานจะดับลง 0 วินาทีต่อมา
[re-draw Status DIV with "Calculating" value, execute LongCalc (lines 1-3)]
คิวหลัง:
- เหตุการณ์: ใหม่วาด DIV สถานะด้วย "คำนวณ" ค่า
[execute LongCalc (lines 1-3)]
คิวหลัง: โปรดทราบว่าเหตุการณ์วาดใหม่นี้อาจเกิดขึ้นจริงก่อนที่สัญญาณเตือนจะดับซึ่งทำงานได้ดีเช่นกัน
- ...
ไชโย! Status DIV เพิ่งได้รับการอัปเดตเป็น "กำลังคำนวณ ... " ก่อนการคำนวณเริ่ม !!!
ด้านล่างเป็นตัวอย่างโค้ดจาก JSFiddle ที่แสดงตัวอย่างเหล่านี้: http://jsfiddle.net/C2YBE/31/ :
รหัส HTML:
<table border=1>
<tr><td><button id='do'>Do long calc - bad status!</button></td>
<td><div id='status'>Not Calculating yet.</div></td>
</tr>
<tr><td><button id='do_ok'>Do long calc - good status!</button></td>
<td><div id='status_ok'>Not Calculating yet.</div></td>
</tr>
</table>
รหัส JavaScript: (ดำเนินการonDomReady
และอาจต้องใช้ jQuery 1.9)
function long_running(status_div) {
var result = 0;
// Use 1000/700/300 limits in Chrome,
// 300/100/100 in IE8,
// 1000/500/200 in FireFox
// I have no idea why identical runtimes fail on diff browsers.
for (var i = 0; i < 1000; i++) {
for (var j = 0; j < 700; j++) {
for (var k = 0; k < 300; k++) {
result = result + i + j + k;
}
}
}
$(status_div).text('calculation done');
}
// Assign events to buttons
$('#do').on('click', function () {
$('#status').text('calculating....');
long_running('#status');
});
$('#do_ok').on('click', function () {
$('#status_ok').text('calculating....');
// This works on IE8. Works in Chrome
// Does NOT work in FireFox 25 with timeout =0 or =1
// DOES work in FF if you change timeout from 0 to 500
window.setTimeout(function (){ long_running('#status_ok') }, 0);
});