ทำไมตัวแปรของฉันไม่เปลี่ยนแปลงหลังจากที่ฉันแก้ไขภายในฟังก์ชั่น? - การอ้างอิงรหัสแบบอะซิงโครนัส


669

รับตัวอย่างต่อไปนี้ทำไมouterScopeVarไม่นิยามในทุกกรณี?

var outerScopeVar;

var img = document.createElement('img');
img.onload = function() {
    outerScopeVar = this.width;
};
img.src = 'lolcat.png';
alert(outerScopeVar);

var outerScopeVar;
setTimeout(function() {
    outerScopeVar = 'Hello Asynchronous World!';
}, 0);
alert(outerScopeVar);

// Example using some jQuery
var outerScopeVar;
$.post('loldog', function(response) {
    outerScopeVar = response;
});
alert(outerScopeVar);

// Node.js example
var outerScopeVar;
fs.readFile('./catdog.html', function(err, data) {
    outerScopeVar = data;
});
console.log(outerScopeVar);

// with promises
var outerScopeVar;
myPromise.then(function (response) {
    outerScopeVar = response;
});
console.log(outerScopeVar);

// geolocation API
var outerScopeVar;
navigator.geolocation.getCurrentPosition(function (pos) {
    outerScopeVar = pos;
});
console.log(outerScopeVar);

ทำไมมันออกมาundefinedในตัวอย่างเหล่านี้ทั้งหมด? ฉันไม่ต้องการวิธีแก้ไขปัญหาฉันต้องการทราบสาเหตุที่เกิดขึ้น


หมายเหตุ:นี่เป็นคำถามที่ยอมรับสำหรับasynchronicity JavaScript อย่าลังเลที่จะปรับปรุงคำถามนี้และเพิ่มตัวอย่างที่ง่ายขึ้นซึ่งชุมชนสามารถระบุได้



@Dukeling ขอบคุณฉันค่อนข้างแน่ใจว่าฉันได้แสดงความคิดเห็นกับลิงค์นั้น แต่เห็นได้ชัดว่ามีบางความคิดเห็นที่ขาดหายไป นอกจากนี้เกี่ยวกับการแก้ไขของคุณ: ฉันเชื่อว่าการมี "canonical" และ "asynchronicity" ในชื่อช่วยเมื่อค้นหาคำถามนี้เพื่อทำเครื่องหมายคำถามอื่นว่าเป็นคนหลอกลวง และแน่นอนว่ามันยังช่วยในการค้นหาคำถามนี้จาก Google เมื่อค้นหาคำอธิบายแบบอะซิงโทรนิก
FabrícioMatté

3
การใส่ความคิดอีกเล็กน้อย "หัวข้อ asynchronicity ที่ยอมรับได้" นั้นค่อนข้างหนักในชื่อเรื่อง "การอ้างอิงรหัสแบบอะซิงโครนัส" นั้นง่ายกว่าและมีวัตถุประสงค์มากกว่า ฉันยังเชื่อว่าคนส่วนใหญ่ค้นหาคำว่า "อะซิงโครนัส" แทนที่จะเป็น "อะซินโครนัส"
FabrícioMatté

1
บางคนเริ่มต้นตัวแปรก่อนการเรียกใช้ฟังก์ชัน วิธีการเกี่ยวกับการเปลี่ยนชื่อเรื่องที่แสดงถึงเช่นกัน? เช่นเดียวกับ "ทำไมตัวแปรของฉันจึงไม่เปลี่ยนแปลงหลังจากที่ฉันแก้ไขภายในฟังก์ชัน" ?
เฟลิกซ์คลิง

ในตัวอย่างโค้ดทั้งหมดที่คุณได้กล่าวถึงข้างต้น "alert (outerScopeVar);" ดำเนินการทันทีขณะที่การกำหนดค่าให้กับ "outerScopeVar" จะเกิดขึ้นภายหลัง (แบบอะซิงโครนัส)
refactor

คำตอบ:


542

หนึ่งคำตอบ: asynchronicity

คำนำ

หัวข้อนี้ซ้ำอย่างน้อยสองสามพันครั้งที่นี่ใน Stack Overflow ดังนั้นก่อนอื่นฉันอยากจะชี้ให้เห็นแหล่งข้อมูลที่มีประโยชน์มาก:

  • คำตอบ @Felix แพรวถึง "วิธีการที่จะกลับมาตอบสนองจากการโทรไม่ตรงกัน" ดูคำตอบที่ยอดเยี่ยมของเขาที่อธิบายการไหลแบบซิงโครนัสและอะซิงโครนัสเช่นเดียวกับส่วน
    @Benjamin Gruenbaum ได้พยายามอธิบาย asynchronicity ในหัวข้อเดียวกันด้วย

  • คำตอบของ @Matt Esch ต่อ "รับข้อมูลจาก fs.readFile"ยังอธิบายถึงความเป็น asynchronicity อย่างมากในลักษณะที่เรียบง่าย


คำตอบของคำถามในมือ

ลองติดตามพฤติกรรมทั่วไปก่อน ในตัวอย่างที่outerScopeVarมีการปรับเปลี่ยนภายในของฟังก์ชั่น ฟังก์ชั่นนั้นไม่ได้ถูกดำเนินการอย่างชัดเจนมันกำลังถูกกำหนดหรือส่งผ่านเป็นอาร์กิวเมนต์ นั่นคือสิ่งที่เราเรียกว่าการเรียกกลับ

ตอนนี้คำถามคือเมื่อโทรกลับที่เรียกว่า?

มันขึ้นอยู่กับกรณี ลองติดตามพฤติกรรมทั่วไปบางอย่างอีกครั้ง:

  • img.onloadอาจถูกเรียกบางครั้งในอนาคตเมื่อ (และถ้า) ภาพโหลดสำเร็จ
  • setTimeoutอาจจะเรียกว่าบางครั้งในอนาคตclearTimeoutหลังจากที่ล่าช้าได้หมดอายุลงและหมดเวลายังไม่ได้ถูกยกเลิกโดย หมายเหตุ: แม้ว่าจะใช้0เป็นความล่าช้าเบราว์เซอร์ทั้งหมดจะมีระยะเวลาการหน่วงเวลาการหมดเวลาขั้นต่ำ (ระบุเป็น 4ms ในข้อมูลจำเพาะ HTML5)
  • การ$.postโทรกลับของjQuery อาจถูกเรียกใช้ในอนาคตเมื่อ (และถ้า) คำขอ Ajax เสร็จสมบูรณ์
  • Node.js fs.readFileอาจถูกเรียกใช้ในอนาคตเมื่อไฟล์ถูกอ่านสำเร็จหรือเกิดข้อผิดพลาด

ในทุกกรณีเรามีการเรียกกลับซึ่งอาจเรียกใช้บางครั้งในอนาคต นี้ "บางครั้งในอนาคต" คือสิ่งที่เราเรียกว่าการไหลไม่ตรงกัน

การประมวลผลแบบอะซิงโครนัสถูกผลักออกจากโฟลว์ซิงโครนัส นั่นคือโค้ดอะซิงโครนัสจะไม่ทำงานในขณะที่สแต็กโค้ดแบบซิงโครนัสกำลังทำงานอยู่ นี่คือความหมายของ JavaScript ที่เป็นเธรดเดี่ยว

โดยเฉพาะอย่างยิ่งเมื่อเอ็นจิน JS ไม่ได้ใช้งาน - ไม่ทำการประมวลผลสแต็กของ (a) โค้ดซิงโครนัส - มันจะสำรวจเหตุการณ์ที่อาจเรียกใช้การโทรกลับแบบอะซิงโครนัส (เช่นหมดเวลาหมดอายุ นี้ถือได้ว่าเป็นเหตุการณ์ห่วง

นั่นคือรหัสแบบอะซิงโครนัสที่ไฮไลต์ในรูปร่างสีแดงที่วาดด้วยมืออาจทำงานหลังจากรหัสซิงโครนัสที่เหลือทั้งหมดในบล็อกรหัสที่เกี่ยวข้องได้ดำเนินการแล้ว:

รหัส async ถูกเน้น

ในระยะสั้นฟังก์ชั่นการโทรกลับถูกสร้างขึ้นพร้อมกัน แต่ดำเนินการแบบอะซิงโครนัส คุณไม่สามารถพึ่งพาการดำเนินการของฟังก์ชั่นอะซิงโครนัสจนกว่าคุณจะรู้ว่าได้ดำเนินการแล้วและจะทำอย่างไร?

มันง่ายจริงๆ ตรรกะที่ขึ้นอยู่กับการเรียกใช้ฟังก์ชั่นแบบอะซิงโครนัสควรเริ่มต้น / เรียกจากภายในฟังก์ชันอะซิงโครนัสนี้ ตัวอย่างเช่นการย้ายalerts และconsole.logs ภายในฟังก์ชั่นการโทรกลับจะเอาท์พุทผลลัพธ์ที่คาดหวังเพราะผลลัพธ์ที่มีอยู่ ณ จุดนั้น

การใช้ตรรกะการโทรกลับของคุณเอง

บ่อยครั้งที่คุณต้องทำสิ่งต่าง ๆ มากขึ้นด้วยผลลัพธ์จากฟังก์ชันแบบอะซิงโครนัสหรือทำสิ่งต่าง ๆ ด้วยผลลัพธ์ขึ้นอยู่กับตำแหน่งที่ฟังก์ชันอะซิงโครนัสถูกเรียก ลองจัดการกับตัวอย่างที่ซับซ้อนกว่านี้หน่อย:

var outerScopeVar;
helloCatAsync();
alert(outerScopeVar);

function helloCatAsync() {
    setTimeout(function() {
        outerScopeVar = 'Nya';
    }, Math.random() * 2000);
}

หมายเหตุ:ฉันกำลังใช้setTimeoutกับการหน่วงเวลาแบบสุ่มเป็นฟังก์ชันอะซิงโครนัสทั่วไปตัวอย่างเดียวกันนี้ใช้กับ Ajax readFile, onloadและการไหลแบบอะซิงโครนัสอื่น ๆ

ตัวอย่างนี้เห็นได้ชัดว่าได้รับความเดือดร้อนจากปัญหาเดียวกันกับตัวอย่างอื่น ๆ ซึ่งไม่ได้รอจนกว่าจะมีการเรียกใช้ฟังก์ชันอะซิงโครนัส

ลองจัดการมันด้วยการใช้ระบบโทรกลับของเราเอง ก่อนอื่นเรากำจัดสิ่งที่น่าเกลียดouterScopeVarซึ่งไร้ประโยชน์โดยสิ้นเชิงในกรณีนี้ จากนั้นเราเพิ่มพารามิเตอร์ที่ยอมรับการโต้แย้งฟังก์ชั่นการโทรกลับของเรา เมื่อการดำเนินการแบบอะซิงโครนัสเสร็จสิ้นเราเรียกการเรียกกลับนี้ว่าผ่านผลลัพธ์ การใช้งาน (โปรดอ่านความคิดเห็นตามลำดับ):

// 1. Call helloCatAsync passing a callback function,
//    which will be called receiving the result from the async operation
helloCatAsync(function(result) {
    // 5. Received the result from the async function,
    //    now do whatever you want with it:
    alert(result);
});

// 2. The "callback" parameter is a reference to the function which
//    was passed as argument from the helloCatAsync call
function helloCatAsync(callback) {
    // 3. Start async operation:
    setTimeout(function() {
        // 4. Finished async operation,
        //    call the callback passing the result as argument
        callback('Nya');
    }, Math.random() * 2000);
}

ข้อมูลโค้ดของตัวอย่างด้านบน:

// 1. Call helloCatAsync passing a callback function,
//    which will be called receiving the result from the async operation
console.log("1. function called...")
helloCatAsync(function(result) {
    // 5. Received the result from the async function,
    //    now do whatever you want with it:
    console.log("5. result is: ", result);
});

// 2. The "callback" parameter is a reference to the function which
//    was passed as argument from the helloCatAsync call
function helloCatAsync(callback) {
    console.log("2. callback here is the function passed as argument above...")
    // 3. Start async operation:
    setTimeout(function() {
    console.log("3. start async operation...")
    console.log("4. finished async operation, calling the callback, passing the result...")
        // 4. Finished async operation,
        //    call the callback passing the result as argument
        callback('Nya');
    }, Math.random() * 2000);
}

ส่วนใหญ่มักจะอยู่ในกรณีใช้งานจริง DOM API และไลบรารีส่วนใหญ่จะมีฟังก์ชันการเรียกกลับ ( helloCatAsyncการนำไปใช้ในตัวอย่างสาธิตนี้) คุณจะต้องผ่านฟังก์ชั่นการโทรกลับและเข้าใจว่ามันจะดำเนินการออกจากกระแสซิงโครนัสและปรับโครงสร้างโค้ดของคุณเพื่อรองรับ

คุณจะสังเกตเห็นว่าเนื่องจากลักษณะแบบอะซิงโครนัสจึงเป็นไปไม่ได้ที่returnค่าจากการไหลแบบอะซิงโครนัสจะกลับไปที่โฟลว์ซิงโครนัสที่การโทรกลับถูกกำหนด

แทนที่จะreturnใช้ค่าจากการโทรกลับแบบอะซิงโครนัสคุณจะต้องใช้รูปแบบการโทรกลับหรือ ... สัญญา

สัญญา

แม้ว่าจะมีวิธีที่จะทำให้นรกการติดต่อกลับเป็นไปอย่างราบรื่นด้วยวานิลลา JS สัญญาที่เพิ่มขึ้นในความนิยมและกำลังได้มาตรฐานใน ES6 (ดูสัญญา - MDN )

สัญญา (aka ฟิวเจอร์ส) ให้เส้นตรงมากขึ้นและน่าอ่านของรหัสอะซิงโครนัส แต่การอธิบายการทำงานทั้งหมดของพวกเขาอยู่นอกขอบเขตของคำถามนี้ แต่ฉันจะปล่อยให้แหล่งข้อมูลที่ยอดเยี่ยมเหล่านี้สำหรับผู้ที่สนใจ:


อ่านเนื้อหาเพิ่มเติมเกี่ยวกับ JavaScript asynchronicity

  • The Art of Node - Callbacksอธิบายรหัสแบบอะซิงโครนัสและการโทรกลับได้เป็นอย่างดีด้วยตัวอย่างของวานิลลา JS และรหัส Node.js เช่นกัน

หมายเหตุ:ฉันทำเครื่องหมายคำตอบนี้เป็น Community Wiki ดังนั้นทุกคนที่มีชื่อเสียงอย่างน้อย 100 คนสามารถแก้ไขและปรับปรุงได้! โปรดปรับปรุงคำตอบนี้หรือส่งคำตอบใหม่โดยสมบูรณ์หากคุณต้องการเช่นกัน

ฉันต้องการเปลี่ยนคำถามนี้เป็นหัวข้อบัญญัติเพื่อตอบปัญหา asynchronicity ที่ไม่เกี่ยวข้องกับ Ajax (มีวิธีตอบกลับจากการโทร AJAX หรือไม่สำหรับเรื่องนั้น) ดังนั้นหัวข้อนี้ต้องการความช่วยเหลือของคุณให้ดีและเป็นประโยชน์ที่สุด !


1
ในตัวอย่างสุดท้ายของคุณมีเหตุผลเฉพาะที่ทำให้คุณใช้ฟังก์ชั่นที่ไม่ระบุชื่อหรือมันจะทำงานเหมือนกันโดยใช้ฟังก์ชั่นที่มีชื่อหรือไม่?
JDelage

1
ตัวอย่างโค้ดแปลกไปเล็กน้อยเมื่อคุณประกาศฟังก์ชั่นหลังจากเรียกใช้ ใช้งานได้เนื่องจากการยกของแน่นอน แต่มันตั้งใจหรือไม่
Bergi

2
มันคือการหยุดชะงัก felix kling กำลังชี้ไปที่คำตอบของคุณและคุณกำลังชี้ไปที่คำตอบของเฟลิกซ์
Mahi

1
คุณต้องเข้าใจว่ารหัสวงกลมสีแดงเป็นเพียง async เพราะมันจะถูกดำเนินการโดยฟังก์ชั่นจาวาสคริปต์ asive NATIVE นี่เป็นคุณสมบัติของเอ็นจิ้น javascript ของคุณไม่ว่าจะเป็น Node.js หรือเบราว์เซอร์ มันเป็น async เพราะมันถูกส่งผ่านในรูปแบบ "การเรียกกลับ" ไปยังฟังก์ชั่นที่เป็นกล่องดำ (นำมาใช้ใน C ฯลฯ .. ) สำหรับนักพัฒนาที่โชคร้ายพวกเขาเป็นอะซิงก์ ... เพียงเพราะ หากคุณต้องการเขียนฟังก์ชั่น async ของคุณเองคุณต้องแฮ็คมันโดยส่งไปที่ SetTimeout (myfunc, 0) คุณควรทำอย่างนั้น? การถกเถียงอีกครั้ง
Sean Anderson

@Fabricio ฉันได้ค้นหาข้อมูลจำเพาะที่ระบุ "> = 4ms clamp" แต่ไม่พบ - ฉันพบว่ามีการพูดถึงกลไกคล้ายกัน (สำหรับการจับสายซ้อนกัน) บน MDN - developer.mozilla.org/en-US/docs / Web / API / … - ไม่มีใครมีลิงก์ไปยังส่วนขวาของข้อกำหนด HTML
Sebi

147

คำตอบของFabrícioคือจุดบน; แต่ฉันต้องการเติมเต็มคำตอบของเขาด้วยเทคนิคที่น้อยกว่าซึ่งเพ่งความสนใจไปที่การเปรียบเทียบเพื่อช่วยอธิบายแนวคิดของความไม่แน่นอน


การเปรียบเทียบ ...

เมื่อวานนี้งานที่ฉันทำต้องการข้อมูลจากเพื่อนร่วมงาน ฉันรังเขา นี่คือวิธีการสนทนา:

ฉัน : สวัสดีบ๊อบผมจำเป็นต้องรู้วิธีการที่เราFoo 'งบาร์ ' วันที่สัปดาห์ที่ผ่านมา จิมต้องการรายงานเกี่ยวกับมันและคุณเป็นคนเดียวที่รู้รายละเอียดเกี่ยวกับมัน

Bob : แน่นอน แต่ใช้เวลาประมาณ 30 นาที

ฉัน : นั่นคือบ๊อบที่ยอดเยี่ยม ให้แหวนกลับมาให้ฉันเมื่อคุณได้รับข้อมูล!

ณ จุดนี้ฉันวางสายโทรศัพท์ เนื่องจากฉันต้องการข้อมูลจาก Bob เพื่อทำรายงานให้สมบูรณ์ฉันจึงออกจากรายงานและไปซื้อกาแฟแทนจากนั้นฉันก็ไปตามอีเมล 40 นาทีต่อมา (บ๊อบช้า) บ๊อบโทรกลับและให้ข้อมูลที่ฉันต้องการ ณ จุดนี้ฉันกลับมาทำงานกับรายงานของฉันเนื่องจากฉันมีข้อมูลทั้งหมดที่ฉันต้องการ


ลองนึกภาพว่าบทสนทนาเป็นแบบนี้แทนไหม

ฉัน : สวัสดีบ๊อบผมจำเป็นต้องรู้วิธีการที่เราFoo 'งบาร์ ' วันที่สัปดาห์ที่ผ่านมา จิมต้องการรายงานเกี่ยวกับมันและคุณเป็นคนเดียวที่รู้รายละเอียดเกี่ยวกับมัน

Bob : แน่นอน แต่ใช้เวลาประมาณ 30 นาที

ฉัน : นั่นคือบ๊อบที่ยอดเยี่ยม ฉันจะรอ.

และฉันนั่งที่นั่นและรอ และรอ และรอ เป็นเวลา 40 นาที ไม่ทำอะไรเลยนอกจากรอ ในที่สุดบ๊อบก็ให้ข้อมูลกับฉันเราวางหูและฉันก็ทำรายงานให้เสร็จ แต่ฉันเสียเวลาในการผลิต 40 นาที


นี่เป็นพฤติกรรมแบบอะซิงโครนัสกับซิงโครนัส

นี่คือสิ่งที่เกิดขึ้นในตัวอย่างทั้งหมดในคำถามของเรา การโหลดอิมเมจการโหลดไฟล์จากดิสก์และการขอเพจผ่าน AJAX นั้นเป็นการดำเนินการที่ช้า (ในบริบทของการคำนวณสมัยใหม่)

แทนที่จะรอให้การดำเนินการช้าเหล่านี้เสร็จสมบูรณ์ JavaScript ช่วยให้คุณลงทะเบียนฟังก์ชั่นการโทรกลับซึ่งจะดำเนินการเมื่อการดำเนินการช้าเสร็จสมบูรณ์ อย่างไรก็ตามในระหว่างนี้ JavaScript จะยังคงเรียกใช้รหัสอื่น ๆ ความจริงที่ว่า JavaScript รันรหัสอื่น ๆในขณะที่รอการดำเนินการช้าจะเสร็จสมบูรณ์ทำให้ลักษณะการทำงานไม่ตรงกัน หากจาวาสคริปต์รอให้การดำเนินการเสร็จสิ้นก่อนที่จะดำเนินการกับรหัสอื่น ๆ นี่จะเป็นลักษณะการทำงานแบบซิงโครนั

var outerScopeVar;    
var img = document.createElement('img');

// Here we register the callback function.
img.onload = function() {
    // Code within this function will be executed once the image has loaded.
    outerScopeVar = this.width;
};

// But, while the image is loading, JavaScript continues executing, and
// processes the following lines of JavaScript.
img.src = 'lolcat.png';
alert(outerScopeVar);

ในโค้ดด้านบนเราจะขอให้โหลด JavaScript lolcat.pngซึ่งเป็นการดำเนินการที่ยุ่งเหยิง ฟังก์ชันการเรียกกลับจะถูกดำเนินการเมื่อการดำเนินการช้านี้เสร็จ แต่ในระหว่างนี้ JavaScript จะประมวลผลบรรทัดถัดไปของรหัส alert(outerScopeVar)กล่าวคือ

นี่คือเหตุผลที่เราเห็นการแจ้งเตือนแสดงundefined; เนื่องจากalert()มีการประมวลผลทันทีแทนที่จะโหลดภาพ

เพื่อแก้ไขรหัสของเราสิ่งที่เราต้องทำคือย้ายalert(outerScopeVar)รหัสไปยังฟังก์ชันการเรียกกลับ ด้วยเหตุนี้เราจึงไม่ต้องการouterScopeVarตัวแปรที่ประกาศเป็นตัวแปรโกลบอลอีกต่อไป

var img = document.createElement('img');

img.onload = function() {
    var localScopeVar = this.width;
    alert(localScopeVar);
};

img.src = 'lolcat.png';

คุณจะมักจะเห็นการเรียกกลับมีการระบุเป็นฟังก์ชั่นเนื่องจากว่าเป็นเพียงวิธี * ใน JavaScript เพื่อกำหนดรหัสบางส่วน แต่ไม่รันมันจนกระทั่งต่อมา

ดังนั้นในตัวอย่างทั้งหมดของเราfunction() { /* Do something */ }การเรียกกลับคือ ในการแก้ไขปัญหาทุกตัวอย่างทั้งหมดที่เราต้องทำคือการย้ายโค้ดที่ต้องการการตอบสนองของการดำเนินการเข้าไปในที่นั่น!

* ในทางเทคนิคคุณสามารถใช้eval()เช่นกัน แต่eval()เป็นความชั่วร้ายสำหรับวัตถุประสงค์นี้


ฉันจะรอผู้โทรได้อย่างไร

ขณะนี้คุณอาจมีรหัสคล้ายกัน

function getWidthOfImage(src) {
    var outerScopeVar;

    var img = document.createElement('img');
    img.onload = function() {
        outerScopeVar = this.width;
    };
    img.src = src;
    return outerScopeVar;
}

var width = getWidthOfImage('lolcat.png');
alert(width);

อย่างไรก็ตามตอนนี้เรารู้ว่าreturn outerScopeVarเกิดขึ้นทันที ก่อนที่onloadฟังก์ชั่นการโทรกลับจะอัปเดตตัวแปร สิ่งนี้นำไปสู่การgetWidthOfImage()กลับมาundefinedและundefinedการแจ้งเตือน

ในการแก้ไขปัญหานี้เราต้องอนุญาตให้ฟังก์ชันการโทรgetWidthOfImage()ลงทะเบียนการโทรกลับจากนั้นย้ายการแจ้งเตือนของความกว้างเพื่อให้อยู่ภายในการติดต่อกลับนั้น

function getWidthOfImage(src, cb) {     
    var img = document.createElement('img');
    img.onload = function() {
        cb(this.width);
    };
    img.src = src;
}

getWidthOfImage('lolcat.png', function (width) {
    alert(width);
});

... เหมือนก่อนโปรดทราบว่าเราสามารถลบตัวแปรโกลบอลได้ (ในกรณีนี้width)


แต่การแจ้งเตือนหรือส่งไปยังคอนโซลมีประโยชน์อย่างไรถ้าคุณต้องการใช้ผลลัพธ์ในการคำนวณอื่นหรือเก็บไว้ในตัวแปรออบเจ็กต์
Ken Ingram

68

ต่อไปนี้เป็นคำตอบที่กระชับยิ่งขึ้นสำหรับผู้ที่กำลังมองหาข้อมูลอ้างอิงอย่างรวดเร็วรวมถึงตัวอย่างบางส่วนโดยใช้สัญญาและ async / await

เริ่มต้นด้วยวิธีการที่ไร้เดียงสา (ที่ไม่ได้ผล) สำหรับฟังก์ชั่นที่เรียกใช้วิธีอะซิงโครนัส (ในกรณีนี้setTimeout) และส่งคืนข้อความ:

function getMessage() {
  var outerScopeVar;
  setTimeout(function() {
    outerScopeVar = 'Hello asynchronous world!';
  }, 0);
  return outerScopeVar;
}
console.log(getMessage());

undefinedได้รับการบันทึกไว้ในกรณีนี้เพราะgetMessageผลตอบแทนก่อนที่จะโทรกลับเรียกว่าและการปรับปรุงsetTimeoutouterScopeVar

สองวิธีหลักในการแก้ปัญหาโดยใช้การโทรกลับและสัญญา :

เรียกกลับ

การเปลี่ยนแปลงที่นี่คือการgetMessageยอมรับcallbackพารามิเตอร์ที่จะถูกเรียกว่าเพื่อส่งผลลัพธ์กลับไปที่รหัสการโทรครั้งเดียวที่มีอยู่

function getMessage(callback) {
  setTimeout(function() {
    callback('Hello asynchronous world!');
  }, 0);
}
getMessage(function(message) {
  console.log(message);
});

สัญญา

สัญญาเป็นทางเลือกที่ยืดหยุ่นกว่าการโทรกลับเนื่องจากสามารถรวมกันได้โดยธรรมชาติเพื่อประสานงานการดำเนินการต่างๆของ async สัญญา / A +การดำเนินงานมาตรฐานมีให้กำเนิดใน Node.js (0.12+) และเบราว์เซอร์ในปัจจุบันหลายคน แต่ยังถูกนำมาใช้ในห้องสมุดเช่นครามและQ

function getMessage() {
  return new Promise(function(resolve, reject) {
    setTimeout(function() {
      resolve('Hello asynchronous world!');
    }, 0);
  });
}

getMessage().then(function(message) {
  console.log(message);  
});

jQuery Deferreds

jQuery มีฟังก์ชั่นที่คล้ายกับสัญญากับ Deferreds

function getMessage() {
  var deferred = $.Deferred();
  setTimeout(function() {
    deferred.resolve('Hello asynchronous world!');
  }, 0);
  return deferred.promise();
}

getMessage().done(function(message) {
  console.log(message);  
});

async / รอคอย

หากสภาพแวดล้อม JavaScript ของคุณมีการสนับสนุนasyncและawait(เช่น Node.js 7.6+) คุณสามารถใช้สัญญาที่ทำasyncหน้าที่ซิงโครไนซ์ภายในฟังก์ชั่น:

function getMessage () {
    return new Promise(function(resolve, reject) {
        setTimeout(function() {
            resolve('Hello asynchronous world!');
        }, 0);
    });
}

async function main() {
    let message = await getMessage();
    console.log(message);
}

main();

ตัวอย่างของคุณเกี่ยวกับสัญญาเป็นสิ่งที่ฉันได้ค้นหาในไม่กี่ชั่วโมงที่ผ่านมา ตัวอย่างของคุณสวยงามและอธิบายสัญญาในเวลาเดียวกัน ทำไมสิ่งนี้ไม่สามารถเชื่อได้ทุกที่
Vincent P

ทั้งหมดนี้ใช้ได้ แต่ถ้าคุณต้องการเรียกใช้ getMessage () พร้อมพารามิเตอร์ คุณจะเขียนด้านบนในสถานการณ์นั้นได้อย่างไร?
Chiwda

2
คุณ @Chiwda function getMessage(param1, param2, callback) {...}เพียงแค่ใส่พารามิเตอร์โทรกลับล่าสุด:
JohnnyHK

ฉันลองasync/awaitตัวอย่างของคุณแต่ฉันพบปัญหา แทนที่จะเป็นการสร้างอินสแตนซ์ให้new Promiseฉัน.Get()โทรออกและดังนั้นจึงไม่สามารถเข้าถึงresolve()วิธีการใด ๆ ดังนั้นฉันgetMessage()จะคืนสัญญาและไม่ใช่ผลลัพธ์ คุณสามารถแก้ไขคำตอบเล็กน้อยเพื่อแสดงไวยากรณ์ที่ใช้งานได้สำหรับสิ่งนี้
InteXX

@InteXX ฉันไม่แน่ใจว่าคุณหมายถึงอะไรเกี่ยวกับการ.Get()โทร น่าจะดีที่สุดในการโพสต์คำถามใหม่
JohnnyHK

52

outerScopeVarที่จะระบุชัดเจนถ้วยหมาย

ฟังก์ชันแบบอะซิงโครนัสเป็นเหมือน ...

การโทรแบบอะซิงโครนัสสำหรับกาแฟ


13
ในขณะที่พยายามทำฟังก์ชั่นอะซิงโครนัสทำหน้าที่ซิงโครนัสจะพยายามดื่มกาแฟในเวลา 1 วินาทีและให้มันเทลงบนตักของคุณใน 1 นาที
Teepeemm

ถ้ามันเป็นการระบุที่ชัดเจนฉันไม่คิดว่าคำถามจะถูกถามใช่ไหม?
บรอกโคลี

2
@ broccoli2000 โดยที่ฉันไม่ได้หมายความว่าคำถามก็เห็นได้ชัด แต่ก็เห็นได้ชัดว่าสิ่งที่ถ้วยหมายในภาพวาด :)
โยฮันเน Fahrenkrug

13

คำตอบอื่น ๆ นั้นยอดเยี่ยมและฉันแค่ต้องการให้คำตอบตรงไปตรงนี้ เพียง จำกัด การโทรแบบอะซิงโครนัส jQuery

การโทร ajax ทั้งหมด (รวมถึง$.getหรือ$.postหรือ$.ajax) เป็นแบบอะซิงโครนัส

พิจารณาตัวอย่างของคุณ

var outerScopeVar;  //line 1
$.post('loldog', function(response) {  //line 2
    outerScopeVar = response;
});
alert(outerScopeVar);  //line 3

การเรียกใช้โค้ดเริ่มต้นจากบรรทัดที่ 1 ประกาศตัวแปรและทริกเกอร์และการเรียกแบบอะซิงโครนัสที่บรรทัดที่ 2 (เช่นคำขอโพสต์) และมันจะดำเนินการทำงานต่อจากบรรทัดที่ 3 โดยไม่ต้องรอคำขอโพสต์

ให้บอกว่าคำขอโพสต์นั้นใช้เวลา 10 วินาทีจึงจะเสร็จสมบูรณ์ค่าของouterScopeVarจะตั้งหลังจาก 10 วินาทีนั้นเท่านั้น

เพื่อลอง

var outerScopeVar; //line 1
$.post('loldog', function(response) {  //line 2, takes 10 seconds to complete
    outerScopeVar = response;
});
alert("Lets wait for some time here! Waiting is fun");  //line 3
alert(outerScopeVar);  //line 4

ตอนนี้เมื่อคุณดำเนินการนี้คุณจะได้รับการแจ้งเตือนที่บรรทัด 3 ตอนนี้รอสักครู่จนกว่าคุณจะแน่ใจว่าคำขอโพสต์คืนค่าบางอย่าง จากนั้นเมื่อคุณคลิกตกลงบนกล่องการแจ้งเตือนการแจ้งเตือนครั้งต่อไปจะพิมพ์ค่าที่คาดไว้เพราะคุณรอ

ในสถานการณ์จริงรหัสกลายเป็น

var outerScopeVar;
$.post('loldog', function(response) {
    outerScopeVar = response;
    alert(outerScopeVar);
});

รหัสทั้งหมดที่ขึ้นอยู่กับการเรียกแบบอะซิงโครนัสถูกย้ายภายในบล็อกแบบอะซิงโครนัสหรือโดยการรอการเรียกแบบอะซิงโครนัส


or by waiting on the asynchronous callsเราจะทำอย่างนั้นได้อย่างไร?
InteXX

@InteXX โดยใช้วิธีการโทรกลับ
Teja

คุณมีตัวอย่างของไวยากรณ์อย่างรวดเร็วหรือไม่?
InteXX

10

ในสถานการณ์ทั้งหมดเหล่านี้outerScopeVarมีการแก้ไขหรือกำหนดค่าแบบอะซิงโครนัสหรือเกิดขึ้นในภายหลัง (รอหรือฟังเหตุการณ์บางอย่างที่จะเกิดขึ้น) ซึ่งการดำเนินการปัจจุบันจะไม่รอดังนั้นกรณีเหล่านี้ทั้งหมดไหลดำเนินการปัจจุบันouterScopeVar = undefined

ลองอภิปรายแต่ละตัวอย่าง (ฉันทำเครื่องหมายส่วนที่เรียกว่าแบบอะซิงโครนัสหรือล่าช้าเพื่อให้เหตุการณ์บางอย่างเกิดขึ้น):

1

ป้อนคำอธิบายรูปภาพที่นี่

ที่นี่เราลงทะเบียน eventlistner ซึ่งจะดำเนินการตามเหตุการณ์นั้น ๆโหลดรูปภาพจากนั้นการประมวลผลปัจจุบันต่อเนื่องกับบรรทัดถัดไปimg.src = 'lolcat.png';และในalert(outerScopeVar);ขณะเดียวกันเหตุการณ์อาจไม่เกิดขึ้น เช่น funtion img.onloadรอให้อิมเมจที่อ้างอิงโหลดอย่างไม่หยุดยั้ง สิ่งนี้จะเกิดขึ้นกับตัวอย่างทั้งหมด - เหตุการณ์อาจแตกต่างกัน

2

2

ที่นี่กิจกรรมหมดเวลามีบทบาทซึ่งจะเรียกใช้ตัวจัดการหลังจากเวลาที่กำหนด ที่นี่มันเป็น0แต่ก็ยังลงทะเบียนเหตุการณ์ไม่ตรงกันมันจะถูกเพิ่มไปยังตำแหน่งสุดท้ายของการEvent Queueดำเนินการซึ่งทำให้ความล่าช้ารับประกัน

3

ป้อนคำอธิบายรูปภาพที่นี่ เวลานี้โทรกลับ Ajax

4

ป้อนคำอธิบายรูปภาพที่นี่

โหนดสามารถพิจารณาเป็นราชาของการเข้ารหัสแบบอะซิงโครนัสฟังก์ชันที่ทำเครื่องหมายไว้จะถูกลงทะเบียนเป็นตัวจัดการการเรียกกลับซึ่งจะถูกเรียกใช้หลังจากอ่านไฟล์ที่ระบุ

5

ป้อนคำอธิบายรูปภาพที่นี่

สัญญาที่ชัดเจน (สิ่งที่จะทำในอนาคต) เป็นแบบอะซิงโครนัส ดูความแตกต่างระหว่าง Deferred, Promise และ Future ใน JavaScript คืออะไร

https://www.quora.com/Whats-the-difference-between-a-promise-and-a-callback-in-Javascript

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.