ฉันจะตอบกลับการตอบกลับจากการโทรแบบอะซิงโครนัสได้อย่างไร
ฉันมีฟังก์ชันfoo
ที่สร้างคำขอแบบอะซิงโครนัส ฉันจะส่งคืนการตอบกลับ / ผลลัพธ์ได้foo
อย่างไร?
ฉันพยายามส่งคืนค่าจากการเรียกกลับรวมทั้งกำหนดผลลัพธ์ให้กับตัวแปรท้องถิ่นภายในฟังก์ชันและส่งคืนค่านั้น แต่ไม่มีวิธีใดที่ส่งคืนการตอบสนองอย่างแท้จริง (พวกเขาทั้งหมดจะคืนค่าundefined
หรือค่าเริ่มต้นของตัวแปรresult
) .
ตัวอย่างการใช้ajax
ฟังก์ชันของ jQuery :
function foo() {
var result;
$.ajax({
url: '...',
success: function(response) {
result = response;
// return response; // <- I tried that one as well
}
});
return result; // It always returns `undefined`
}
ตัวอย่างการใช้ node.js:
function foo() {
var result;
fs.readFile("path/to/file", function(err, data) {
result = data;
// return data; // <- I tried that one as well
});
return result; // It always returns `undefined`
}
ตัวอย่างการใช้then
บล็อกของคำสัญญา:
function foo() {
var result;
fetch(url).then(function(response) {
result = response;
// return response; // <- I tried that one as well
});
return result; // It always returns `undefined`
}
คำตอบ
→สำหรับคำอธิบายทั่วไปเพิ่มเติมเกี่ยวกับพฤติกรรม async พร้อมตัวอย่างต่างๆโปรดดู เหตุใดตัวแปรของฉันจึงไม่เปลี่ยนแปลงหลังจากที่ฉันแก้ไขภายในฟังก์ชัน - การอ้างอิงรหัสอะซิงโครนัส
→หากคุณเข้าใจปัญหาแล้วให้ข้ามไปที่แนวทางแก้ไขที่เป็นไปได้ด้านล่าง
ปัญหา
ในอาแจ็กซ์ย่อมาไม่ตรงกัน นั่นหมายถึงการส่งคำขอ (หรือรับการตอบกลับ) จะถูกนำออกจากขั้นตอนการดำเนินการตามปกติ ในตัวอย่างของคุณส่งกลับทันทีและคำสั่งถัดไปจะถูกเรียกใช้งานก่อนฟังก์ชันที่คุณส่งผ่านเมื่อเรียกกลับด้วยซ้ำ$.ajax
return result;
success
นี่คือการเปรียบเทียบซึ่งหวังว่าจะทำให้ความแตกต่างระหว่างการไหลแบบซิงโครนัสและอะซิงโครนัสชัดเจนขึ้น:
ซิงโครนัส
ลองนึกภาพคุณโทรหาเพื่อนและขอให้เขาหาอะไรให้คุณ แม้ว่าอาจใช้เวลาสักครู่ แต่คุณก็รอโทรศัพท์และจ้องมองไปในอวกาศจนกว่าเพื่อนของคุณจะให้คำตอบที่คุณต้องการ
สิ่งเดียวกันนี้เกิดขึ้นเมื่อคุณเรียกใช้ฟังก์ชันที่มีรหัส "ปกติ":
function findItem() {
var item;
while(item_not_found) {
// search
}
return item;
}
var item = findItem();
// Do something with item
doSomethingElse();
แม้ว่าfindItem
อาจใช้เวลาดำเนินการนาน แต่โค้ดใด ๆ ที่ตามมาvar item = findItem();
ต้องรอจนกว่าฟังก์ชันจะส่งคืนผลลัพธ์
อะซิงโครนัส
คุณโทรหาเพื่อนของคุณอีกครั้งด้วยเหตุผลเดียวกัน แต่คราวนี้คุณบอกเขาว่าคุณกำลังรีบและเขาควรโทรกลับทางโทรศัพท์มือถือของคุณ คุณวางสายออกจากบ้านและทำทุกอย่างที่คุณวางแผนจะทำ เมื่อเพื่อนของคุณโทรกลับคุณกำลังจัดการกับข้อมูลที่เขาให้กับคุณ
นั่นคือสิ่งที่เกิดขึ้นเมื่อคุณร้องขอ Ajax
findItem(function(item) {
// Do something with the item
});
doSomethingElse();
แทนที่จะรอการตอบกลับการดำเนินการจะดำเนินต่อไปทันทีและคำสั่งหลังจากเรียกใช้ Ajax แล้ว เพื่อให้ได้รับการตอบกลับในที่สุดคุณต้องจัดเตรียมฟังก์ชันที่จะถูกเรียกเมื่อได้รับการตอบกลับแล้วการโทรกลับ (สังเกตว่ามีอะไรโทรกลับไหม) คำสั่งใด ๆ ที่เกิดขึ้นหลังจากการโทรนั้นจะถูกดำเนินการก่อนที่จะมีการเรียกกลับ
โซลูชัน (s)
ยอมรับลักษณะอะซิงโครนัสของ JavaScript! แม้ว่าการดำเนินการแบบอะซิงโครนัสบางอย่างจะให้การทำงานร่วมกันแบบซิงโครนัส (เช่นเดียวกับ "Ajax") แต่โดยทั่วไปแล้วไม่แนะนำให้ใช้โดยเฉพาะอย่างยิ่งในบริบทของเบราว์เซอร์
ทำไมคุณถึงไม่ดี?
JavaScript ทำงานในเธรด UI ของเบราว์เซอร์และกระบวนการที่ทำงานเป็นเวลานานจะล็อก UI ทำให้ไม่ตอบสนอง นอกจากนี้ยังมีขีด จำกัด สูงสุดของเวลาในการดำเนินการสำหรับ JavaScript และเบราว์เซอร์จะถามผู้ใช้ว่าจะดำเนินการต่อหรือไม่
ทั้งหมดนี้เป็นประสบการณ์การใช้งานที่แย่มาก ผู้ใช้จะไม่สามารถบอกได้ว่าทุกอย่างทำงานได้ดีหรือไม่ นอกจากนี้ผลกระทบจะแย่ลงสำหรับผู้ใช้ที่มีการเชื่อมต่อช้า
ต่อไปนี้เราจะดูวิธีแก้ปัญหาที่แตกต่างกันสามวิธีที่สร้างขึ้นจากกัน:
- สัญญากับ
async/await
(ES2017 + พร้อมใช้งานในเบราว์เซอร์รุ่นเก่าหากคุณใช้ทรานสไพเลอร์หรือรีเจนเนอเรเตอร์) - การโทรกลับ (เป็นที่นิยมในโหนด)
- สัญญากับ
then()
(ES2015 + พร้อมใช้งานในเบราว์เซอร์รุ่นเก่าหากคุณใช้หนึ่งในไลบรารีคำสัญญาจำนวนมาก)
ทั้งสามมีอยู่ในเบราว์เซอร์ปัจจุบันและโหนด 7+
ES2017 +: สัญญากับ async/await
ECMAScript เวอร์ชันที่เปิดตัวในปี 2017 ได้เปิดตัวการสนับสนุนระดับไวยากรณ์สำหรับฟังก์ชันอะซิงโครนัส ด้วยความช่วยเหลือของasync
และawait
คุณสามารถเขียนแบบอะซิงโครนัสใน "สไตล์ซิงโครนัส" รหัสยังคงเป็นแบบอะซิงโครนัส แต่อ่าน / ทำความเข้าใจได้ง่ายกว่า
async/await
สร้างขึ้นจากคำสัญญา: async
ฟังก์ชันจะส่งคืนคำสัญญาเสมอ await
"แกะ" คำสัญญาและส่งผลให้ค่าที่สัญญาได้รับการแก้ไขหรือแสดงข้อผิดพลาดหากคำสัญญาถูกปฏิเสธ
สำคัญ:คุณสามารถใช้ได้await
ภายในasync
ฟังก์ชันเท่านั้น ตอนนี้await
ยังไม่รองรับระดับบนสุดดังนั้นคุณอาจต้องสร้าง async IIFE ( ทันทีเรียกใช้ฟังก์ชันนิพจน์ ) เพื่อเริ่มasync
บริบท
คุณสามารถอ่านเพิ่มเติมเกี่ยวกับasyncและawaitใน MDN
นี่คือตัวอย่างที่สร้างขึ้นจากความล่าช้าด้านบน:
// Using 'superagent' which will return a promise.
var superagent = require('superagent')
// This is isn't declared as `async` because it already returns a promise
function delay() {
// `delay` returns a promise
return new Promise(function(resolve, reject) {
// Only `delay` is able to resolve or reject the promise
setTimeout(function() {
resolve(42); // After 3 seconds, resolve the promise with value 42
}, 3000);
});
}
async function getAllBooks() {
try {
// GET a list of book IDs of the current user
var bookIDs = await superagent.get('/user/books');
// wait for 3 seconds (just for the sake of this example)
await delay();
// GET information about each book
return await superagent.get('/books/ids='+JSON.stringify(bookIDs));
} catch(error) {
// If any of the awaited promises was rejected, this catch block
// would catch the rejection reason
return null;
}
}
// Start an IIFE to use `await` at the top level
(async function(){
let books = await getAllBooks();
console.log(books);
})();
รองรับเบราว์เซอร์และเวอร์ชันโหนดasync/await
ปัจจุบัน คุณยังสามารถรองรับสภาพแวดล้อมที่เก่ากว่าได้โดยการเปลี่ยนรหัสของคุณเป็น ES5 ด้วยความช่วยเหลือของตัวสร้างใหม่ (หรือเครื่องมือที่ใช้ตัวสร้างใหม่เช่นBabel )
ให้ฟังก์ชันยอมรับการโทรกลับ
การเรียกกลับคือเมื่อฟังก์ชัน 1 ถูกส่งไปยังฟังก์ชัน 2 ฟังก์ชัน 2 สามารถเรียกใช้ฟังก์ชัน 1 ได้ทุกเมื่อที่พร้อม ในบริบทของกระบวนการอะซิงโครนัสการเรียกกลับจะถูกเรียกเมื่อใดก็ตามที่กระบวนการอะซิงโครนัสเสร็จสิ้น โดยปกติผลลัพธ์จะถูกส่งไปยังการเรียกกลับ
ในตัวอย่างคำถามคุณสามารถfoo
ยอมรับการโทรกลับและใช้เป็นการsuccess
โทรกลับได้ อย่างนี้
var result = foo();
// Code that depends on 'result'
กลายเป็น
foo(function(result) {
// Code that depends on 'result'
});
ที่นี่เรากำหนดฟังก์ชัน "อินไลน์" แต่คุณสามารถส่งผ่านการอ้างอิงฟังก์ชันใดก็ได้:
function myCallback(result) {
// Code that depends on 'result'
}
foo(myCallback);
foo
ตัวเองถูกกำหนดไว้ดังนี้:
function foo(callback) {
$.ajax({
// ...
success: callback
});
}
callback
จะอ้างถึงฟังก์ชั่นที่เราผ่านไปเมื่อเราเรียกมันและเราผ่านมันไปfoo
success
กล่าวคือเมื่อคำขอ Ajax สำเร็จ$.ajax
จะเรียกcallback
และส่งการตอบกลับไปยังการเรียกกลับ (ซึ่งสามารถอ้างถึงได้result
เนื่องจากนี่คือวิธีที่เรากำหนดการเรียกกลับ)
คุณยังสามารถประมวลผลการตอบกลับก่อนที่จะส่งไปยังการโทรกลับ:
function foo(callback) {
$.ajax({
// ...
success: function(response) {
// For example, filter the response
callback(filtered_response);
}
});
}
การเขียนโค้ดโดยใช้การเรียกกลับง่ายกว่าที่คิด ท้ายที่สุด JavaScript ในเบราว์เซอร์เป็นเหตุการณ์ที่ขับเคลื่อนด้วยเหตุการณ์ (เหตุการณ์ DOM) เป็นอย่างมาก การได้รับการตอบสนอง Ajax ไม่ใช่สิ่งอื่นใดนอกจากเหตุการณ์
ความยากลำบากอาจเกิดขึ้นเมื่อคุณต้องทำงานกับรหัสของบุคคลที่สาม แต่ปัญหาส่วนใหญ่สามารถแก้ไขได้เพียงแค่คิดผ่านขั้นตอนของแอปพลิเคชัน
ES2015 +: สัญญากับตอนนั้น ()
สัญญา APIเป็นคุณลักษณะใหม่ของ ECMAScript 6 (ES2015) แต่มันก็มีดีการสนับสนุนเบราว์เซอร์แล้ว นอกจากนี้ยังมีไลบรารีจำนวนมากที่ใช้ Promises API มาตรฐานและมีวิธีการเพิ่มเติมเพื่อลดความสะดวกในการใช้งานและองค์ประกอบของฟังก์ชันแบบอะซิงโครนัส (เช่นbluebird )
คำสัญญาเป็นภาชนะสำหรับคุณค่าในอนาคต เมื่อสัญญาได้รับค่า (ได้รับการแก้ไข ) หรือเมื่อถูกยกเลิก ( ปฏิเสธ ) จะแจ้ง "ผู้ฟัง" ทั้งหมดที่ต้องการเข้าถึงค่านี้
ข้อได้เปรียบเหนือการโทรกลับธรรมดาคือช่วยให้คุณสามารถแยกรหัสของคุณได้และง่ายต่อการเขียน
นี่คือตัวอย่างของการใช้คำสัญญา:
function delay() {
// `delay` returns a promise
return new Promise(function(resolve, reject) {
// Only `delay` is able to resolve or reject the promise
setTimeout(function() {
resolve(42); // After 3 seconds, resolve the promise with value 42
}, 3000);
});
}
delay()
.then(function(v) { // `delay` returns a promise
console.log(v); // Log the value once it is resolved
})
.catch(function(v) {
// Or do something else if it is rejected
// (it would not happen in this example, since `reject` is not called).
});
นำไปใช้กับการโทร Ajax ของเราเราสามารถใช้คำสัญญาเช่นนี้:
function ajax(url) {
return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.onload = function() {
resolve(this.responseText);
};
xhr.onerror = reject;
xhr.open('GET', url);
xhr.send();
});
}
ajax("/echo/json")
.then(function(result) {
// Code depending on result
})
.catch(function() {
// An error occurred
});
การอธิบายข้อดีทั้งหมดที่สัญญาว่าข้อเสนออยู่นอกเหนือขอบเขตของคำตอบนี้ แต่ถ้าคุณเขียนโค้ดใหม่คุณควรพิจารณาอย่างจริงจัง พวกเขาให้ความเป็นนามธรรมที่ดีเยี่ยมและการแยกรหัสของคุณ
ข้อมูลเพิ่มเติมเกี่ยวกับสัญญา: หิน HTML5 - สัญญา JavaScript
หมายเหตุด้านข้าง: วัตถุที่รอการตัดบัญชีของ jQuery
ออบเจ็กต์รอการตัดบัญชีเป็นการใช้งานตามสัญญาที่กำหนดเองของ jQuery (ก่อนที่ Promise API จะเป็นมาตรฐาน) พวกเขาทำตัวเหมือนคำสัญญา แต่เปิดเผย API ที่แตกต่างกันเล็กน้อย
ทุกเมธอด Ajax ของ jQuery จะส่งคืน "ออบเจ็กต์ที่รอการตัดบัญชี" อยู่แล้ว (ซึ่งเป็นสัญญาของอ็อบเจ็กต์ที่รอการตัดบัญชี) ซึ่งคุณสามารถกลับมาจากฟังก์ชันของคุณได้
function ajax() {
return $.ajax(...);
}
ajax().done(function(result) {
// Code depending on result
}).fail(function() {
// An error occurred
});
หมายเหตุด้านข้าง: สัญญา gotchas
โปรดทราบว่าสัญญาและออบเจ็กต์รอการตัดบัญชีเป็นเพียงภาชนะบรรจุสำหรับมูลค่าในอนาคตไม่ใช่มูลค่าในตัวมันเอง ตัวอย่างเช่นสมมติว่าคุณมีสิ่งต่อไปนี้:
function checkPassword() {
return $.ajax({
url: '/password',
data: {
username: $('#username').val(), password: $('#password').val()
},
type: 'POST',
dataType: 'json'
});
}
if (checkPassword()) {
// Tell the user they're logged in
}
รหัสนี้เข้าใจผิดเกี่ยวกับปัญหาอะซิงโครไนซ์ข้างต้น โดยเฉพาะ$.ajax()
อย่าตรึงโค้ดในขณะที่ตรวจสอบหน้า '/ รหัสผ่าน' บนเซิร์ฟเวอร์ของคุณ - มันจะส่งคำขอไปยังเซิร์ฟเวอร์และในขณะที่รอมันจะส่งคืนอ็อบเจ็กต์ jQuery Ajax Deferred ทันทีไม่ใช่การตอบสนองจากเซิร์ฟเวอร์ นั่นหมายความว่าif
คำสั่งจะได้รับวัตถุที่รอการตัดบัญชีเสมอถือว่าเป็นtrue
และดำเนินการต่อราวกับว่าผู้ใช้เข้าสู่ระบบไม่ดี
แต่การแก้ไขนั้นง่ายมาก:
checkPassword()
.done(function(r) {
if (r) {
// Tell the user they're logged in
} else {
// Tell the user their password was bad
}
})
.fail(function(x) {
// Tell the user something bad happened
});
ไม่แนะนำ: การโทร "Ajax" แบบซิงโครนัส
ดังที่ฉันได้กล่าวไปการดำเนินการแบบอะซิงโครนัส (!) บางอย่างมีคู่แบบซิงโครนัส ฉันไม่สนับสนุนการใช้งาน แต่เพื่อความสมบูรณ์นี่คือวิธีที่คุณจะโทรแบบซิงโครนัส:
ไม่มี jQuery
ถ้าคุณใช้โดยตรงXMLHttpRequestวัตถุผ่านเป็นอาร์กิวเมนต์ที่สามไปfalse
.open
jQuery
หากคุณใช้jQueryคุณสามารถตั้งค่าasync
ตัวเลือกเป็นfalse
. โปรดทราบว่าตัวเลือกนี้เลิกใช้แล้วตั้งแต่ jQuery 1.8 จากนั้นคุณสามารถใช้การsuccess
เรียกกลับหรือเข้าถึงresponseText
คุณสมบัติของวัตถุ jqXHR :
function foo() {
var jqXHR = $.ajax({
//...
async: false
});
return jqXHR.responseText;
}
ถ้าคุณใช้วิธี jQuery Ajax อื่น ๆ เช่น$.get
, $.getJSON
ฯลฯ คุณจะต้องเปลี่ยนไป$.ajax
(ตั้งแต่คุณเท่านั้นที่สามารถผ่านการกำหนดค่าพารามิเตอร์ไป$.ajax
)
โปรดทราบ! ไม่สามารถสร้างคำขอJSONP แบบซิงโครนัสได้ JSONP โดยธรรมชาติแล้วมักจะไม่ตรงกันเสมอ (อีกเหตุผลหนึ่งที่ไม่ต้องพิจารณาตัวเลือกนี้ด้วยซ้ำ)
หากคุณไม่ได้ใช้ jQuery ในโค้ดของคุณคำตอบนี้เหมาะสำหรับคุณ
รหัสของคุณควรเป็นบรรทัดต่อไปนี้:
function foo() {
var httpRequest = new XMLHttpRequest();
httpRequest.open('GET', "/echo/json");
httpRequest.send();
return httpRequest.responseText;
}
var result = foo(); // always ends up being 'undefined'
Felix Kling ทำงานได้ดีในการเขียนคำตอบสำหรับผู้ที่ใช้ jQuery สำหรับ AJAX ฉันตัดสินใจที่จะให้ทางเลือกสำหรับผู้ที่ไม่ได้เป็น
( หมายเหตุสำหรับผู้ที่ใช้fetchAPI ใหม่Angular หรือสัญญาฉันได้เพิ่มคำตอบด้านล่าง )
สิ่งที่คุณกำลังเผชิญ
นี่คือสรุปสั้น ๆ ของ "คำอธิบายของปัญหา" จากคำตอบอื่น ๆ หากคุณไม่แน่ใจหลังจากอ่านสิ่งนี้แล้วให้อ่านสิ่งนั้น
ใน AJAX ย่อมาไม่ตรงกัน นั่นหมายถึงการส่งคำขอ (หรือรับการตอบกลับ) จะถูกนำออกจากขั้นตอนการดำเนินการตามปกติ ในตัวอย่างของคุณส่งกลับทันทีและคำสั่งถัดไปจะถูกเรียกใช้งานก่อนฟังก์ชันที่คุณส่งผ่านเมื่อเรียกกลับด้วยซ้ำ.sendreturn result;
success
ซึ่งหมายความว่าเมื่อคุณกลับมาตัวฟังที่คุณกำหนดไว้ยังไม่ได้ดำเนินการซึ่งหมายความว่ายังไม่ได้กำหนดค่าที่คุณส่งคืน
นี่คือการเปรียบเทียบง่ายๆ
function getFive(){
var a;
setTimeout(function(){
a=5;
},10);
return a;
}
(ซอ)
ค่าของการa
ส่งคืนundefined
มาจากa=5
ส่วนที่ยังไม่ได้ดำเนินการ AJAX ทำเช่นนี้คุณจะส่งคืนค่าก่อนที่เซิร์ฟเวอร์จะมีโอกาสบอกเบราว์เซอร์ของคุณว่าค่านั้นคืออะไร
วิธีแก้ปัญหานี้วิธีหนึ่งที่เป็นไปได้คือการเขียนโค้ดซ้ำโดยบอกโปรแกรมของคุณว่าจะทำอย่างไรเมื่อการคำนวณเสร็จสิ้น
function onComplete(a){ // When the code completes, do this
alert(a);
}
function getFive(whenDone){
var a;
setTimeout(function(){
a=5;
whenDone(a);
},10);
}
นี้เรียกว่าCPS โดยทั่วไปเรากำลังgetFive
ดำเนินการเพื่อดำเนินการเมื่อเสร็จสิ้นเรากำลังบอกรหัสของเราว่าจะตอบสนองอย่างไรเมื่อเหตุการณ์เสร็จสิ้น (เช่นการโทร AJAX ของเราหรือในกรณีนี้คือการหมดเวลา)
การใช้งานจะเป็น:
getFive(onComplete);
ซึ่งควรแจ้งเตือน "5" ที่หน้าจอ (ซอ) .
การแก้ปัญหาที่เป็นไปได้
โดยทั่วไปมีสองวิธีในการแก้ปัญหานี้:
- ทำการโทร AJAX แบบซิงโครนัส (เรียกว่า SJAX)
- ปรับโครงสร้างรหัสของคุณให้ทำงานอย่างถูกต้องกับการโทรกลับ
1. Synchronous AJAX - อย่าทำ !!
สำหรับ AJAX แบบซิงโครนัสอย่าทำ! คำตอบของเฟลิกซ์ทำให้เกิดข้อโต้แย้งที่น่าสนใจว่าเหตุใดจึงเป็นความคิดที่ไม่ดี สรุปแล้วมันจะหยุดเบราว์เซอร์ของผู้ใช้จนกว่าเซิร์ฟเวอร์จะตอบกลับและสร้างประสบการณ์ผู้ใช้ที่แย่มาก นี่คือบทสรุปสั้น ๆ อีกเรื่องที่นำมาจาก MDN เกี่ยวกับสาเหตุ:
XMLHttpRequest รองรับทั้งการสื่อสารแบบซิงโครนัสและอะซิงโครนัส อย่างไรก็ตามโดยทั่วไปคำขอแบบอะซิงโครนัสควรเป็นที่ต้องการสำหรับคำขอแบบซิงโครนัสด้วยเหตุผลด้านประสิทธิภาพ
ในระยะสั้นการร้องขอแบบซิงโครนัสจะบล็อกการเรียกใช้รหัส ... ... ซึ่งอาจทำให้เกิดปัญหาร้ายแรง ...
หากคุณต้องทำคุณสามารถส่งธงได้: นี่คือวิธี:
var request = new XMLHttpRequest();
request.open('GET', 'yourURL', false); // `false` makes the request synchronous
request.send(null);
if (request.status === 200) {// That's HTTP for 'ok'
console.log(request.responseText);
}
2. รหัสปรับโครงสร้าง
ให้ฟังก์ชันของคุณยอมรับการโทรกลับ ในตัวอย่างรหัสfoo
สามารถทำได้เพื่อยอมรับการโทรกลับ เราจะบอกรหัสของเราว่าจะตอบสนองอย่างไรเมื่อfoo
เสร็จสมบูรณ์
ดังนั้น:
var result = foo();
// code that depends on `result` goes here
กลายเป็น:
foo(function(result) {
// code that depends on `result`
});
ที่นี่เราส่งผ่านฟังก์ชันที่ไม่ระบุตัวตน แต่เราสามารถส่งผ่านการอ้างอิงไปยังฟังก์ชันที่มีอยู่ได้อย่างง่ายดายทำให้ดูเหมือนว่า:
function myHandler(result) {
// code that depends on `result`
}
foo(myHandler);
สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับวิธีการออกแบบการโทรกลับประเภทนี้ให้ตรวจสอบคำตอบของ Felix
ตอนนี้เรามากำหนด foo ตัวเองเพื่อดำเนินการตามนั้น
function foo(callback) {
var httpRequest = new XMLHttpRequest();
httpRequest.onload = function(){ // when the request is loaded
callback(httpRequest.responseText);// we're calling our method
};
httpRequest.open('GET', "/echo/json");
httpRequest.send();
}
(ซอ)
ตอนนี้เราได้ทำให้ฟังก์ชัน foo ของเรายอมรับการดำเนินการเพื่อทำงานเมื่อ AJAX เสร็จสมบูรณ์เราสามารถขยายเพิ่มเติมได้โดยตรวจสอบว่าสถานะการตอบกลับไม่ใช่ 200 และดำเนินการตามนั้น (สร้างตัวจัดการความล้มเหลวและอื่น ๆ ) แก้ปัญหาของเราได้อย่างมีประสิทธิภาพ
หากคุณยังคงมีปัญหาในการทำความเข้าใจสิ่งนี้โปรดอ่านคู่มือการเริ่มต้นใช้งาน AJAXที่ MDN
XMLHttpRequest 2 (ก่อนอื่นอ่านคำตอบจาก Benjamin Gruenbaum & Felix Kling )
หากคุณไม่ได้ใช้ jQuery และต้องการ XMLHttpRequest 2 สั้น ๆ ที่ดีซึ่งใช้งานได้กับเบราว์เซอร์สมัยใหม่และบนเบราว์เซอร์มือถือฉันขอแนะนำให้ใช้วิธีนี้:
function ajax(a, b, c){ // URL, callback, just a placeholder
c = new XMLHttpRequest;
c.open('GET', a);
c.onload = b;
c.send()
}
อย่างที่เห็น:
- มันสั้นกว่าฟังก์ชั่นอื่น ๆ ทั้งหมดที่ระบุไว้
- การโทรกลับถูกตั้งค่าโดยตรง (ดังนั้นจึงไม่มีการปิดที่ไม่จำเป็นเพิ่มเติม)
- ใช้ onload ใหม่ (ดังนั้นคุณไม่ต้องตรวจสอบสถานะ readystate &&)
- มีบางสถานการณ์ที่ฉันจำไม่ได้ว่าทำให้ XMLHttpRequest 1 น่ารำคาญ
มีสองวิธีในการรับการตอบสนองของการเรียก Ajax นี้ (สามวิธีโดยใช้ชื่อตัวแปร XMLHttpRequest):
ง่ายที่สุด:
this.response
หรือหากเหตุผลบางประการคุณbind()
โทรกลับไปที่ชั้นเรียน:
e.target.response
ตัวอย่าง:
function callback(e){
console.log(this.response);
}
ajax('URL', callback);
หรือ (ฟังก์ชั่นไม่ระบุตัวตนที่ดีกว่ามักเป็นปัญหา):
ajax('URL', function(e){console.log(this.response)});
ไม่มีอะไรง่ายกว่า
ตอนนี้บางคนอาจจะบอกว่าควรใช้ onreadystatechange หรือแม้แต่ชื่อตัวแปร XMLHttpRequest นั่นผิด
ตรวจสอบคุณสมบัติขั้นสูง XMLHttpRequest
รองรับเบราว์เซอร์ที่ทันสมัยทั้งหมด * และฉันสามารถยืนยันได้ในขณะที่ฉันใช้แนวทางนี้เนื่องจากมี XMLHttpRequest 2 ฉันไม่เคยมีปัญหาใด ๆ ในเบราว์เซอร์ทั้งหมดที่ฉันใช้
onreadystatechange มีประโยชน์ก็ต่อเมื่อคุณต้องการรับส่วนหัวในสถานะ 2
การใช้XMLHttpRequest
ชื่อตัวแปรเป็นข้อผิดพลาดใหญ่อีกประการหนึ่งเนื่องจากคุณต้องดำเนินการเรียกกลับภายในการปิด onload / oreadystatechange มิฉะนั้นคุณจะทำมันหาย
ตอนนี้ถ้าคุณต้องการบางสิ่งที่ซับซ้อนมากขึ้นโดยใช้โพสต์และ FormData คุณสามารถขยายฟังก์ชันนี้ได้อย่างง่ายดาย:
function x(a, b, e, d, c){ // URL, callback, method, formdata or {key:val},placeholder
c = new XMLHttpRequest;
c.open(e||'get', a);
c.onload = b;
c.send(d||null)
}
อีกครั้ง ... มันเป็นฟังก์ชั่นที่สั้นมาก แต่รับและโพสต์
ตัวอย่างการใช้งาน:
x(url, callback); // By default it's get so no need to set
x(url, callback, 'post', {'key': 'val'}); // No need to set post data
หรือส่งผ่านองค์ประกอบแบบเต็ม ( document.getElementsByTagName('form')[0]
):
var fd = new FormData(form);
x(url, callback, 'post', fd);
หรือตั้งค่าที่กำหนดเอง:
var fd = new FormData();
fd.append('key', 'val')
x(url, callback, 'post', fd);
อย่างที่คุณเห็นฉันไม่ได้ใช้การซิงค์ ... มันเป็นสิ่งที่ไม่ดี
ต้องบอกว่า ... ทำไมไม่ทำวิธีง่ายๆล่ะ?
ดังที่กล่าวไว้ในความคิดเห็นการใช้ข้อผิดพลาด && ซิงโครนัสจะทำลายประเด็นของคำตอบโดยสิ้นเชิง วิธีใดเป็นวิธีสั้น ๆ ที่ดีในการใช้ Ajax ในวิธีที่เหมาะสม
ตัวจัดการข้อผิดพลาด
function x(a, b, e, d, c){ // URL, callback, method, formdata or {key:val}, placeholder
c = new XMLHttpRequest;
c.open(e||'get', a);
c.onload = b;
c.onerror = error;
c.send(d||null)
}
function error(e){
console.log('--Error--', this.type);
console.log('this: ', this);
console.log('Event: ', e)
}
function displayAjax(e){
console.log(e, this);
}
x('WRONGURL', displayAjax);
ในสคริปต์ด้านบนคุณมีตัวจัดการข้อผิดพลาดซึ่งกำหนดไว้แบบคงที่ดังนั้นจึงไม่กระทบต่อฟังก์ชัน ตัวจัดการข้อผิดพลาดสามารถใช้กับฟังก์ชันอื่น ๆ ได้เช่นกัน
แต่การที่จะได้รับจริงๆออกข้อผิดพลาดเพียงวิธีคือการเขียน URL ที่ไม่ถูกต้องในกรณีที่เบราว์เซอร์ทุกโยนข้อผิดพลาด
ตัวจัดการข้อผิดพลาดอาจมีประโยชน์หากคุณตั้งค่าส่วนหัวที่กำหนดเองตั้งค่า responseType เป็น blob array buffer หรืออะไรก็ได้ ...
แม้ว่าคุณจะส่ง "POSTAPAPAP" เป็นวิธีการ แต่ก็ไม่เกิดข้อผิดพลาด
แม้ว่าคุณจะส่ง 'fdggdgilfdghfldj' เป็น formdata แต่ก็ไม่เกิดข้อผิดพลาด
ในกรณีแรกที่มีข้อผิดพลาดอยู่ภายในdisplayAjax()
ภายใต้การเป็นthis.statusText
Method not Allowed
ในกรณีที่สองมันใช้งานได้ง่าย คุณต้องตรวจสอบที่ฝั่งเซิร์ฟเวอร์ว่าคุณส่งข้อมูลโพสต์ที่ถูกต้องหรือไม่
ไม่อนุญาตให้ข้ามโดเมนเกิดข้อผิดพลาดโดยอัตโนมัติ
ในการตอบสนองข้อผิดพลาดไม่มีรหัสข้อผิดพลาด
มีเพียงสิ่งthis.type
ที่ถูกตั้งค่าเป็นข้อผิดพลาด
ทำไมต้องเพิ่มตัวจัดการข้อผิดพลาดหากคุณไม่สามารถควบคุมข้อผิดพลาดได้โดยสิ้นเชิง? displayAjax()
ส่วนใหญ่ของข้อผิดพลาดจะถูกส่งกลับภายในนี้ในฟังก์ชันการเรียกกลับ
ดังนั้น: ไม่จำเป็นต้องตรวจสอบข้อผิดพลาดว่าคุณสามารถคัดลอกและวาง URL ได้อย่างถูกต้องหรือไม่ ;)
PS: ในการทดสอบครั้งแรกที่ฉันเขียน x ('x', displayAjax) ... และมันก็ได้รับการตอบสนองโดยสิ้นเชิง ... ??? ดังนั้นฉันจึงตรวจสอบโฟลเดอร์ที่เป็นที่ตั้งของ HTML และมีไฟล์ชื่อ 'x.xml' ดังนั้นแม้ว่าคุณจะลืมนามสกุลของไฟล์ของคุณ XMLHttpRequest 2 จะพบว่า ฉันฮ่า ๆ
อ่านไฟล์แบบซิงโครนัส
อย่าทำอย่างนั้น
หากคุณต้องการบล็อกเบราว์เซอร์สักครู่ให้โหลด.txt
ไฟล์ซิงโครนัสขนาดใหญ่ที่ดี
function omg(a, c){ // URL
c = new XMLHttpRequest;
c.open('GET', a, true);
c.send();
return c; // Or c.response
}
ตอนนี้คุณสามารถทำได้
var res = omg('thisIsGonnaBlockThePage.txt');
ไม่มีวิธีอื่นในการทำเช่นนี้ในลักษณะที่ไม่ใช่อะซิงโครนัส (ใช่กับ setTimeout loop ... แต่จริงจังเหรอ?)
อีกประเด็นหนึ่งคือ ... ถ้าคุณทำงานกับ API หรือแค่ไฟล์ในรายการของคุณเองหรืออะไรก็ตามที่คุณมักจะใช้ฟังก์ชั่นที่แตกต่างกันสำหรับแต่ละคำขอ ...
เฉพาะในกรณีที่คุณมีหน้าที่โหลด XML / JSON เดียวกันหรืออะไรก็ตามที่คุณต้องการเพียงฟังก์ชันเดียว ในกรณีนั้นให้แก้ไขฟังก์ชัน Ajax เล็กน้อยและแทนที่ b ด้วยฟังก์ชันพิเศษของคุณ
ฟังก์ชั่นด้านบนมีไว้สำหรับการใช้งานพื้นฐาน
หากคุณต้องการขยายฟังก์ชัน ...
ใช่คุณสามารถ.
ฉันใช้ API จำนวนมากและหนึ่งในฟังก์ชันแรกที่ฉันรวมเข้ากับทุกหน้า HTML เป็นฟังก์ชัน Ajax แรกในคำตอบนี้ด้วย GET เท่านั้น ...
แต่คุณสามารถทำสิ่งต่างๆมากมายด้วย XMLHttpRequest 2:
ฉันสร้างตัวจัดการการดาวน์โหลด (ใช้ช่วงทั้งสองด้านพร้อมประวัติย่อโปรแกรมอ่านไฟล์ระบบไฟล์) ตัวแปลงตัวปรับขนาดรูปภาพต่างๆโดยใช้ผ้าใบเติมข้อมูลฐานข้อมูล SQL ของเว็บด้วย base64images และอื่น ๆ อีกมากมาย ... แต่ในกรณีเหล่านี้คุณควรสร้างฟังก์ชันเฉพาะสำหรับสิ่งนั้น จุดประสงค์ ... บางครั้งคุณต้องการหยดบัฟเฟอร์อาร์เรย์คุณสามารถตั้งค่าส่วนหัวแทนที่ mimetype และมีอื่น ๆ อีกมากมาย ...
แต่คำถามนี่คือวิธีส่งคืนการตอบกลับ Ajax ... (ฉันเพิ่มวิธีง่ายๆ)
หากคุณกำลังใช้คำสัญญาคำตอบนี้เหมาะสำหรับคุณ
ซึ่งหมายความว่า AngularJS, jQuery (พร้อมการเลื่อน), การแทนที่ของ XHR ดั้งเดิม (การดึงข้อมูล), EmberJS, การบันทึกของ BackboneJS หรือไลบรารีโหนดใด ๆ ที่ส่งคืนสัญญา
รหัสของคุณควรเป็นบรรทัดต่อไปนี้:
function foo() {
var data;
// or $.get(...).then, or request(...).then, or query(...).then
fetch("/echo/json").then(function(response){
data = response.json();
});
return data;
}
var result = foo(); // result is always undefined no matter what.
Felix Kling ทำงานได้ดีในการเขียนคำตอบสำหรับผู้ที่ใช้ jQuery พร้อมการโทรกลับสำหรับ AJAX ฉันมีคำตอบสำหรับ XHR ดั้งเดิม คำตอบนี้มีไว้สำหรับการใช้งานทั่วไปของสัญญาทั้งในส่วนหน้าหรือส่วนหลัง
ประเด็นหลัก
รูปแบบที่เห็นพ้องด้วย JavaScript ในเบราว์เซอร์และบนเซิร์ฟเวอร์ด้วย NodeJS / io.js เป็นตรงกันและปฏิกิริยา
เมื่อใดก็ตามที่คุณเรียกใช้เมธอดที่ส่งคืนคำสัญญาตัวthen
จัดการจะถูกเรียกใช้งานแบบอะซิงโครนัสเสมอนั่นคือหลังจากโค้ดด้านล่างซึ่งไม่ได้อยู่ใน.then
ตัวจัดการ
ซึ่งหมายความว่าเมื่อคุณกลับมาจัดการคุณได้กำหนดไว้ไม่ได้ดำเนินการเลย ในทางกลับกันหมายความว่าค่าที่คุณส่งกลับมาไม่ได้ถูกตั้งค่าเป็นค่าที่ถูกต้องทันเวลาdata
then
นี่คือการเปรียบเทียบง่ายๆสำหรับปัญหา:
function getFive(){
var data;
setTimeout(function(){ // set a timer for one second in the future
data = 5; // after a second, do this
}, 1000);
return data;
}
document.body.innerHTML = getFive(); // `undefined` here and not 5
ค่าของdata
คือundefined
เนื่องจากdata = 5
ส่วนยังไม่ได้ดำเนินการ มีแนวโน้มที่จะดำเนินการในไม่กี่วินาที แต่เมื่อถึงเวลานั้นจะไม่เกี่ยวข้องกับค่าที่ส่งคืน
เนื่องจากการดำเนินการยังไม่เกิดขึ้น (AJAX, การเรียกเซิร์ฟเวอร์, IO, ตัวจับเวลา) คุณจะส่งคืนค่าก่อนที่คำขอจะมีโอกาสบอกรหัสของคุณว่าค่านั้นคืออะไร
วิธีแก้ปัญหานี้วิธีหนึ่งที่เป็นไปได้คือการเขียนโค้ดซ้ำโดยบอกโปรแกรมของคุณว่าจะทำอย่างไรเมื่อการคำนวณเสร็จสิ้น สัญญาจะเปิดใช้งานสิ่งนี้อย่างแข็งขันโดยเป็นไปตามธรรมชาติชั่วคราว (ตามเวลา)
สรุปคำสัญญาอย่างรวดเร็ว
สัญญาเป็นมูลค่าเมื่อเวลาผ่านไป สัญญามีสถานะพวกเขาเริ่มต้นด้วยการรอดำเนินการโดยไม่มีค่าและสามารถชำระเพื่อ:
- เติมเต็มความหมายว่าการคำนวณเสร็จเรียบร้อยแล้ว
- ปฏิเสธหมายความว่าการคำนวณล้มเหลว
คำสัญญาสามารถเปลี่ยนสถานะได้เพียงครั้งเดียวหลังจากนั้นสัญญาจะยังคงอยู่ในสถานะเดิมตลอดไป คุณสามารถแนบthen
ตัวจัดการเพื่อสัญญาว่าจะดึงคุณค่าและจัดการข้อผิดพลาด then
ตัวจัดการอนุญาตให้มีการเชื่อมโยงการโทร สัญญาถูกสร้างขึ้นโดยใช้ API ที่พวกเขากลับ ตัวอย่างเช่นการเปลี่ยน AJAX ที่ทันสมัยกว่าfetch
หรือ$.get
สัญญาการคืนสินค้าของ jQuery
เมื่อเราเรียกร้อง.then
คำสัญญาและส่งคืนบางสิ่งจากสิ่งนั้น - เราได้รับสัญญาสำหรับมูลค่าที่ดำเนินการแล้ว ถ้าเราคืนคำสัญญาอีกครั้งเราจะได้รับสิ่งที่น่าอัศจรรย์ แต่มาจับม้าของเรากันเถอะ
ด้วยคำสัญญา
มาดูกันว่าเราจะแก้ปัญหาข้างต้นด้วยคำสัญญาได้อย่างไร ขั้นแรกให้แสดงความเข้าใจของเราเกี่ยวกับสถานะสัญญาจากด้านบนโดยใช้ตัวสร้างสัญญาเพื่อสร้างฟังก์ชันหน่วงเวลา:
function delay(ms){ // takes amount of milliseconds
// returns a new promise
return new Promise(function(resolve, reject){
setTimeout(function(){ // when the time is up
resolve(); // change the promise to the fulfilled state
}, ms);
});
}
ตอนนี้หลังจากที่เราแปลง setTimeout เพื่อใช้คำสัญญาเราสามารถใช้then
เพื่อทำให้มันนับได้:
function delay(ms){ // takes amount of milliseconds
// returns a new promise
return new Promise(function(resolve, reject){
setTimeout(function(){ // when the time is up
resolve(); // change the promise to the fulfilled state
}, ms);
});
}
function getFive(){
// we're RETURNING the promise, remember, a promise is a wrapper over our value
return delay(100).then(function(){ // when the promise is ready
return 5; // return the value 5, promises are all about return values
})
}
// we _have_ to wrap it like this in the call site, we can't access the plain value
getFive().then(function(five){
document.body.innerHTML = five;
});
โดยทั่วไปแทนการกลับค่าที่เราไม่สามารถทำได้เนื่องจากรูปแบบการทำงานพร้อมกัน - เรากำลังกลับเสื้อคลุมสำหรับค่าที่เราสามารถแกะthen
ด้วย มันเหมือนกับกล่องที่คุณสามารถเปิดthen
ได้
ใช้สิ่งนี้
สิ่งนี้เหมือนกับการเรียก API ดั้งเดิมของคุณคุณสามารถ:
function foo() {
// RETURN the promise
return fetch("/echo/json").then(function(response){
return response.json(); // process it inside the `then`
});
}
foo().then(function(response){
// access the value inside the `then`
})
มันก็ใช้ได้เช่นกัน เราได้เรียนรู้ว่าเราไม่สามารถคืนค่าจากการโทรแบบอะซิงโครนัสที่มีอยู่แล้ว แต่เราสามารถใช้คำสัญญาและเชื่อมโยงกับการประมวลผลได้ ตอนนี้เรารู้วิธีส่งคืนการตอบกลับจากการโทรแบบอะซิงโครนัส
ES2015 (ES6)
ES6 แนะนำเครื่องกำเนิดไฟฟ้าซึ่งเป็นฟังก์ชันที่สามารถกลับมาอยู่ตรงกลางแล้วกลับสู่จุดเดิม โดยทั่วไปจะมีประโยชน์สำหรับลำดับตัวอย่างเช่น:
function* foo(){ // notice the star, this is ES6 so new browsers/node/io only
yield 1;
yield 2;
while(true) yield 3;
}
เป็นฟังก์ชันที่ส่งคืนตัววนซ้ำบนลำดับ1,2,3,3,3,3,....
ซึ่งสามารถทำซ้ำได้ แม้ว่าสิ่งนี้จะน่าสนใจในตัวเองและเปิดช่องให้มีความเป็นไปได้มากมาย แต่ก็มีกรณีที่น่าสนใจเป็นพิเศษ
หากลำดับที่เราสร้างเป็นลำดับของการกระทำแทนที่จะเป็นตัวเลข - เราสามารถหยุดฟังก์ชันชั่วคราวเมื่อใดก็ตามที่มีการดำเนินการและรอให้ฟังก์ชันนั้นดำเนินการต่อ ดังนั้นแทนที่จะเป็นลำดับของตัวเลขเราต้องการลำดับของค่าในอนาคตนั่นคือคำสัญญา
เคล็ดลับที่ค่อนข้างยุ่งยาก แต่ทรงพลังนี้ช่วยให้เราเขียนโค้ดอะซิงโครนัสในลักษณะซิงโครนัสได้ มี "นักวิ่ง" หลายคนที่ทำสิ่งนี้ให้กับคุณการเขียนหนึ่งเป็นโค้ดสั้น ๆ ไม่กี่บรรทัด แต่อยู่นอกเหนือขอบเขตของคำตอบนี้ ฉันจะใช้ครามเป็นPromise.coroutine
ที่นี่ แต่มีห่ออื่น ๆ เช่นหรือco
Q.async
var foo = coroutine(function*(){
var data = yield fetch("/echo/json"); // notice the yield
// code here only executes _after_ the request is done
return data.json(); // data is defined
});
วิธีนี้ส่งคืนคำสัญญาซึ่งเราสามารถใช้จากโครูทีนอื่น ๆ ตัวอย่างเช่น:
var main = coroutine(function*(){
var bar = yield foo(); // wait our earlier coroutine, it returns a promise
// server call done here, code below executes when done
var baz = yield fetch("/api/users/"+bar.userid); // depends on foo's result
console.log(baz); // runs after both requests done
});
main();
ES2016 (ES7)
ใน ES7 นี่เป็นมาตรฐานเพิ่มเติมมีข้อเสนอมากมายในขณะนี้ แต่คุณสามารถawait
สัญญาได้ในทั้งหมด นี่เป็นเพียง "น้ำตาล" (ไวยากรณ์ที่ดีกว่า) สำหรับข้อเสนอ ES6 ด้านบนโดยการเพิ่มasync
และawait
คำหลัก ทำตัวอย่างข้างต้น:
async function foo(){
var data = await fetch("/echo/json"); // notice the await
// code here only executes _after_ the request is done
return data.json(); // data is defined
}
มันยังคงคืนคำสัญญาเหมือนเดิม :)
คุณใช้ Ajax อย่างไม่ถูกต้อง แนวคิดคือไม่ให้มันส่งคืนอะไรเลย แต่ให้ส่งข้อมูลไปยังสิ่งที่เรียกว่าฟังก์ชันเรียกกลับซึ่งจัดการข้อมูลแทน
นั่นคือ:
function handleData( responseData ) {
// Do what you want with the data
console.log(responseData);
}
$.ajax({
url: "hi.php",
...
success: function ( data, status, XHR ) {
handleData(data);
}
});
การส่งคืนสิ่งใด ๆ ในตัวจัดการการส่งจะไม่ทำอะไรเลย คุณต้องส่งต่อข้อมูลหรือทำสิ่งที่คุณต้องการโดยตรงภายในฟังก์ชันความสำเร็จ
วิธีแก้ปัญหาที่ง่ายที่สุดคือสร้างฟังก์ชัน JavaScript และเรียกมันว่า Ajax success
callback
function callServerAsync(){
$.ajax({ url: '...', success: function(response) { successCallback(response); } }); } function successCallback(responseObj){ // Do something like read the response and show data alert(JSON.stringify(responseObj)); // Only applicable to JSON response } function foo(callback) { $.ajax({
url: '...',
success: function(response) {
return callback(null, response);
}
});
}
var result = foo(function(err, result){
if (!err)
console.log(result);
});
ฉันจะตอบด้วยการ์ตูนวาดด้วยมือที่ดูน่ากลัว ภาพที่สองคือสาเหตุที่result
อยู่undefined
ในตัวอย่างโค้ดของคุณ
เชิงมุม 1
สำหรับผู้ที่ใช้AngularJSสามารถจัดการกับสถานการณ์นี้ได้โดยใช้Promises
.
นี่มันบอกว่า
คำสัญญาสามารถใช้กับฟังก์ชันอะซิงโครนัสที่ไม่ตรงกันและอนุญาตให้หนึ่งเชื่อมโยงหลายฟังก์ชันเข้าด้วยกัน
คุณสามารถหาคำอธิบายที่ดีที่นี่ยัง
ตัวอย่างที่พบในเอกสารที่กล่าวถึงด้านล่าง
promiseB = promiseA.then(
function onSuccess(result) {
return result + 1;
}
,function onError(err) {
//Handle error
}
);
// promiseB will be resolved immediately after promiseA is resolved
// and its value will be the result of promiseA incremented by 1.
Angular2 และใหม่กว่า
ในAngular2
ที่มีลักษณะตัวอย่างต่อไปนี้ แต่ขอแนะนำให้ใช้กับObservables
Angular2
search(term: string) {
return this.http
.get(`https://api.spotify.com/v1/search?q=${term}&type=artist`)
.map((response) => response.json())
.toPromise();
}
คุณสามารถบริโภคได้ด้วยวิธีนี้
search() {
this.searchService.search(this.searchField.value)
.then((result) => {
this.result = result.artists.items;
})
.catch((error) => console.error(error));
}
ดูโพสต์ต้นฉบับได้ที่นี่ แต่ typescript ไม่รองรับes6 Promises ดั้งเดิมหากคุณต้องการใช้คุณอาจต้องใช้ปลั๊กอินสำหรับสิ่งนั้น
นอกจากนี้ที่นี่เป็นสัญญาที่สเปคกำหนดที่นี่
คำตอบส่วนใหญ่ที่นี่ให้คำแนะนำที่เป็นประโยชน์เมื่อคุณมีการดำเนินการ async เดียว แต่บางครั้งสิ่งนี้จะเกิดขึ้นเมื่อคุณต้องการดำเนินการแบบอะซิงโครนัสสำหรับแต่ละรายการในอาร์เรย์หรือโครงสร้างอื่น ๆ ที่คล้ายรายการ สิ่งล่อใจคือการทำสิ่งนี้:
// WRONG
var results = [];
theArray.forEach(function(entry) {
doSomethingAsync(entry, function(result) {
results.push(result);
});
});
console.log(results); // E.g., using them, returning them, etc.
ตัวอย่าง:
// WRONG
var theArray = [1, 2, 3];
var results = [];
theArray.forEach(function(entry) {
doSomethingAsync(entry, function(result) {
results.push(result);
});
});
console.log("Results:", results); // E.g., using them, returning them, etc.
function doSomethingAsync(value, callback) {
console.log("Starting async operation for " + value);
setTimeout(function() {
console.log("Completing async operation for " + value);
callback(value * 2);
}, Math.floor(Math.random() * 200));
}
.as-console-wrapper {
max-height: 100% !important;
}
สาเหตุที่ไม่ได้ผลคือการเรียกกลับจากdoSomethingAsync
ยังไม่ทำงานตามเวลาที่คุณพยายามใช้ผลลัพธ์
ดังนั้นหากคุณมีอาร์เรย์ (หรือรายการบางประเภท) และต้องการดำเนินการแบบ async สำหรับแต่ละรายการคุณมีสองตัวเลือก: ดำเนินการแบบขนาน (ทับซ้อนกัน) หรือเป็นชุด (ทีละรายการตามลำดับ)
ขนาน
คุณสามารถเริ่มต้นทั้งหมดและติดตามจำนวนการโทรกลับที่คุณคาดหวังจากนั้นใช้ผลลัพธ์เมื่อคุณได้รับการติดต่อกลับจำนวนมากดังกล่าว:
var results = [];
var expecting = theArray.length;
theArray.forEach(function(entry, index) {
doSomethingAsync(entry, function(result) {
results[index] = result;
if (--expecting === 0) {
// Done!
console.log("Results:", results); // E.g., using the results
}
});
});
ตัวอย่าง:
var theArray = [1, 2, 3];
var results = [];
var expecting = theArray.length;
theArray.forEach(function(entry, index) {
doSomethingAsync(entry, function(result) {
results[index] = result;
if (--expecting === 0) {
// Done!
console.log("Results:", results); // E.g., using the results
}
});
});
function doSomethingAsync(value, callback) {
console.log("Starting async operation for " + value);
setTimeout(function() {
console.log("Completing async operation for " + value);
callback(value * 2);
}, Math.floor(Math.random() * 200));
}
.as-console-wrapper {
max-height: 100% !important;
}
(เราสามารถใช้expecting
และใช้งานresults.length === theArray.length
ได้ แต่นั่นทำให้เราเปิดรับความเป็นไปได้ที่theArray
เปลี่ยนแปลงไปในขณะที่การโทรยังคงค้างอยู่ ... )
สังเกตว่าเราใช้index
from forEach
เพื่อบันทึกผลลัพธ์ในresults
ตำแหน่งเดียวกับรายการที่เกี่ยวข้องแม้ว่าผลลัพธ์จะออกมาไม่เป็นระเบียบก็ตาม (เนื่องจากการเรียก async ไม่จำเป็นต้องเสร็จสมบูรณ์ตามลำดับที่เริ่มต้น)
แต่ถ้าคุณต้องการส่งคืนผลลัพธ์เหล่านั้นจากฟังก์ชันล่ะ? ดังที่คำตอบอื่น ๆ ได้ชี้ให้เห็นว่าคุณทำไม่ได้ คุณต้องให้ฟังก์ชันของคุณยอมรับและโทรกลับ (หรือคืนสัญญา ) นี่คือเวอร์ชันโทรกลับ:
function doSomethingWith(theArray, callback) {
var results = [];
var expecting = theArray.length;
theArray.forEach(function(entry, index) {
doSomethingAsync(entry, function(result) {
results[index] = result;
if (--expecting === 0) {
// Done!
callback(results);
}
});
});
}
doSomethingWith(theArray, function(results) {
console.log("Results:", results);
});
ตัวอย่าง:
function doSomethingWith(theArray, callback) {
var results = [];
var expecting = theArray.length;
theArray.forEach(function(entry, index) {
doSomethingAsync(entry, function(result) {
results[index] = result;
if (--expecting === 0) {
// Done!
callback(results);
}
});
});
}
doSomethingWith([1, 2, 3], function(results) {
console.log("Results:", results);
});
function doSomethingAsync(value, callback) {
console.log("Starting async operation for " + value);
setTimeout(function() {
console.log("Completing async operation for " + value);
callback(value * 2);
}, Math.floor(Math.random() * 200));
}
.as-console-wrapper {
max-height: 100% !important;
}
หรือนี่คือเวอร์ชันที่ส่งคืน a Promise
แทน:
function doSomethingWith(theArray) {
return new Promise(function(resolve) {
var results = [];
var expecting = theArray.length;
theArray.forEach(function(entry, index) {
doSomethingAsync(entry, function(result) {
results[index] = result;
if (--expecting === 0) {
// Done!
resolve(results);
}
});
});
});
}
doSomethingWith(theArray).then(function(results) {
console.log("Results:", results);
});
แน่นอนหากdoSomethingAsync
ส่งข้อผิดพลาดเราจะใช้reject
เพื่อปฏิเสธสัญญาเมื่อเราได้รับข้อผิดพลาด)
ตัวอย่าง:
function doSomethingWith(theArray) {
return new Promise(function(resolve) {
var results = [];
var expecting = theArray.length;
theArray.forEach(function(entry, index) {
doSomethingAsync(entry, function(result) {
results[index] = result;
if (--expecting === 0) {
// Done!
resolve(results);
}
});
});
});
}
doSomethingWith([1, 2, 3]).then(function(results) {
console.log("Results:", results);
});
function doSomethingAsync(value, callback) {
console.log("Starting async operation for " + value);
setTimeout(function() {
console.log("Completing async operation for " + value);
callback(value * 2);
}, Math.floor(Math.random() * 200));
}
.as-console-wrapper {
max-height: 100% !important;
}
(หรืออีกทางเลือกหนึ่งคุณสามารถสร้างกระดาษห่อหุ้มเพื่อdoSomethingAsync
คืนสัญญาจากนั้นทำด้านล่าง ... )
หากdoSomethingAsync
ให้สัญญากับคุณคุณสามารถใช้Promise.all:
function doSomethingWith(theArray) {
return Promise.all(theArray.map(function(entry) {
return doSomethingAsync(entry);
}));
}
doSomethingWith(theArray).then(function(results) {
console.log("Results:", results);
});
หากคุณรู้ว่าdoSomethingAsync
จะเพิกเฉยต่ออาร์กิวเมนต์ที่สองและสามคุณสามารถส่งต่อได้โดยตรงmap
( map
เรียกการเรียกกลับด้วยอาร์กิวเมนต์สามข้อ แต่คนส่วนใหญ่จะใช้อาร์กิวเมนต์แรกเป็นส่วนใหญ่เท่านั้น):
function doSomethingWith(theArray) {
return Promise.all(theArray.map(doSomethingAsync));
}
doSomethingWith(theArray).then(function(results) {
console.log("Results:", results);
});
ตัวอย่าง:
function doSomethingWith(theArray) {
return Promise.all(theArray.map(doSomethingAsync));
}
doSomethingWith([1, 2, 3]).then(function(results) {
console.log("Results:", results);
});
function doSomethingAsync(value) {
console.log("Starting async operation for " + value);
return new Promise(function(resolve) {
setTimeout(function() {
console.log("Completing async operation for " + value);
resolve(value * 2);
}, Math.floor(Math.random() * 200));
});
}
.as-console-wrapper {
max-height: 100% !important;
}
สังเกตว่าPromise.all
แก้ไขคำสัญญาด้วยอาร์เรย์ของผลลัพธ์ของคำสัญญาทั้งหมดที่คุณให้ไว้เมื่อทั้งหมดได้รับการแก้ไขหรือปฏิเสธคำสัญญาเมื่อคำสัญญาแรกที่คุณให้ไว้ปฏิเสธ
ชุด
สมมติว่าคุณไม่ต้องการให้การดำเนินการเป็นแบบคู่ขนาน? หากคุณต้องการเรียกใช้ทีละรายการคุณต้องรอให้แต่ละการดำเนินการเสร็จสิ้นก่อนที่จะเริ่มดำเนินการต่อไป นี่คือตัวอย่างของฟังก์ชันที่ทำเช่นนั้นและเรียกการโทรกลับด้วยผลลัพธ์:
function doSomethingWith(theArray, callback) {
var results = [];
doOne(0);
function doOne(index) {
if (index < theArray.length) {
doSomethingAsync(theArray[index], function(result) {
results.push(result);
doOne(index + 1);
});
} else {
// Done!
callback(results);
}
}
}
doSomethingWith(theArray, function(results) {
console.log("Results:", results);
});
(เนื่องจากเรากำลังทำงานในซีรีส์เราจึงสามารถใช้งานได้results.push(result)
เนื่องจากเรารู้ว่าเราจะไม่ได้ผลลัพธ์ที่ไม่เป็นระเบียบในด้านบนเราสามารถใช้ได้results[index] = result;
แต่ในบางตัวอย่างต่อไปนี้เราไม่มีดัชนี ใช้.)
ตัวอย่าง:
function doSomethingWith(theArray, callback) {
var results = [];
doOne(0);
function doOne(index) {
if (index < theArray.length) {
doSomethingAsync(theArray[index], function(result) {
results.push(result);
doOne(index + 1);
});
} else {
// Done!
callback(results);
}
}
}
doSomethingWith([1, 2, 3], function(results) {
console.log("Results:", results);
});
function doSomethingAsync(value, callback) {
console.log("Starting async operation for " + value);
setTimeout(function() {
console.log("Completing async operation for " + value);
callback(value * 2);
}, Math.floor(Math.random() * 200));
}
.as-console-wrapper {
max-height: 100% !important;
}
(หรืออีกครั้งสร้างเสื้อคลุมเพื่อdoSomethingAsync
ให้สัญญากับคุณและทำด้านล่าง ... )
หากdoSomethingAsync
ให้คำมั่นสัญญาแก่คุณหากคุณสามารถใช้ไวยากรณ์ ES2017 + (อาจใช้ตัวส่งสัญญาณเช่นBabel ) คุณสามารถใช้asyncฟังก์ชันที่มีfor-ofและawait:
async function doSomethingWith(theArray) {
const results = [];
for (const entry of theArray) {
results.push(await doSomethingAsync(entry));
}
return results;
}
doSomethingWith(theArray).then(results => {
console.log("Results:", results);
});
ตัวอย่าง:
async function doSomethingWith(theArray) {
const results = [];
for (const entry of theArray) {
results.push(await doSomethingAsync(entry));
}
return results;
}
doSomethingWith([1, 2, 3]).then(function(results) {
console.log("Results:", results);
});
function doSomethingAsync(value) {
console.log("Starting async operation for " + value);
return new Promise(function(resolve) {
setTimeout(function() {
console.log("Completing async operation for " + value);
resolve(value * 2);
}, Math.floor(Math.random() * 200));
});
}
.as-console-wrapper {
max-height: 100% !important;
}
หากคุณไม่สามารถใช้ไวยากรณ์ ES2017 + ได้ (ยัง) คุณสามารถใช้รูปแบบในรูปแบบ"ลดสัญญา" (ซึ่งซับซ้อนกว่าการลดสัญญาตามปกติเนื่องจากเราไม่ได้ส่งผลลัพธ์จากที่หนึ่งไปสู่อีกรูปแบบหนึ่ง แต่แทนที่จะเป็น รวบรวมผลลัพธ์ในอาร์เรย์):
function doSomethingWith(theArray) {
return theArray.reduce(function(p, entry) {
return p.then(function(results) {
return doSomethingAsync(entry).then(function(result) {
results.push(result);
return results;
});
});
}, Promise.resolve([]));
}
doSomethingWith(theArray).then(function(results) {
console.log("Results:", results);
});
ตัวอย่าง:
function doSomethingWith(theArray) {
return theArray.reduce(function(p, entry) {
return p.then(function(results) {
return doSomethingAsync(entry).then(function(result) {
results.push(result);
return results;
});
});
}, Promise.resolve([]));
}
doSomethingWith([1, 2, 3]).then(function(results) {
console.log("Results:", results);
});
function doSomethingAsync(value) {
console.log("Starting async operation for " + value);
return new Promise(function(resolve) {
setTimeout(function() {
console.log("Completing async operation for " + value);
resolve(value * 2);
}, Math.floor(Math.random() * 200));
});
}
.as-console-wrapper {
max-height: 100% !important;
}
... ซึ่งยุ่งยากน้อยกว่าด้วยฟังก์ชัน ES2015 + arrow :
function doSomethingWith(theArray) {
return theArray.reduce((p, entry) => p.then(results => doSomethingAsync(entry).then(result => {
results.push(result);
return results;
})), Promise.resolve([]));
}
doSomethingWith(theArray).then(results => {
console.log("Results:", results);
});
ตัวอย่าง:
function doSomethingWith(theArray) {
return theArray.reduce((p, entry) => p.then(results => doSomethingAsync(entry).then(result => {
results.push(result);
return results;
})), Promise.resolve([]));
}
doSomethingWith([1, 2, 3]).then(function(results) {
console.log("Results:", results);
});
function doSomethingAsync(value) {
console.log("Starting async operation for " + value);
return new Promise(function(resolve) {
setTimeout(function() {
console.log("Completing async operation for " + value);
resolve(value * 2);
}, Math.floor(Math.random() * 200));
});
}
.as-console-wrapper {
max-height: 100% !important;
}
ดูตัวอย่างนี้:
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope,$http) {
var getJoke = function(){
return $http.get('http://api.icndb.com/jokes/random').then(function(res){
return res.data.value;
});
}
getJoke().then(function(res) {
console.log(res.joke);
});
});
อย่างที่คุณเห็นgetJoke
กำลังส่งคืนสัญญาที่แก้ไขแล้ว(จะได้รับการแก้ไขเมื่อกลับมาres.data.value
) ดังนั้นคุณจึงรอจนกว่าคำขอ$ http.getจะเสร็จสมบูรณ์จากนั้นจึงดำเนินการconsole.log (res.joke) (เป็นโฟลว์อะซิงโครนัสปกติ)
นี่คือ plnkr:
http://embed.plnkr.co/XlNR7HpCaIhJxskMJfSg/
วิธี ES6 (async - รอ)
(function(){
async function getJoke(){
let response = await fetch('http://api.icndb.com/jokes/random');
let data = await response.json();
return data.value;
}
getJoke().then((joke) => {
console.log(joke);
});
})();
นี่เป็นหนึ่งในสถานที่ที่มีสองวิธีในการผูกข้อมูลหรือจัดเก็บแนวคิดที่ใช้ในเฟรมเวิร์ก JavaScript ใหม่จำนวนมากจะทำงานได้ดีสำหรับคุณ ...
ดังนั้นหากคุณใช้Angular, Reactหรือเฟรมเวิร์กอื่น ๆ ที่ทำสองวิธีในการผูกข้อมูลหรือจัดเก็บแนวคิดปัญหานี้ได้รับการแก้ไขสำหรับคุณดังนั้นในคำง่ายๆผลลัพธ์ของคุณจึงundefined
อยู่ในขั้นตอนแรกดังนั้นคุณจะได้รับresult = undefined
ก่อนที่คุณจะได้รับ ข้อมูลจากนั้นทันทีที่คุณได้รับผลลัพธ์จะได้รับการอัปเดตและได้รับการกำหนดค่าใหม่ซึ่งการตอบสนองของการโทร Ajax ของคุณ ...
แต่คุณสามารถทำได้อย่างไรในjavascriptหรือjQuery ที่บริสุทธิ์เช่นที่คุณถามในคำถามนี้?
คุณสามารถใช้โทรกลับ , สัญญาและเมื่อเร็ว ๆ นี้ที่สังเกตจะจัดการกับมันสำหรับคุณเช่นในสัญญาที่เรามีฟังก์ชั่นบางอย่างเช่นsuccess()
หรือthen()
ซึ่งจะต้องถูกประหารชีวิตเมื่อข้อมูลของคุณพร้อมสำหรับคุณเช่นเดียวกันกับการเรียกกลับหรือสมัครฟังก์ชั่นในการสังเกต
ตัวอย่างเช่นในกรณีของคุณที่คุณใช้jQueryคุณสามารถทำสิ่งนี้ได้:
$(document).ready(function(){ function foo() { $.ajax({url: "api/data", success: function(data){
fooDone(data); //after we have data, we pass it to fooDone
}});
};
function fooDone(data) {
console.log(data); //fooDone has the data and console.log it
};
foo(); //call happens here
});
สำหรับการศึกษาข้อมูลเพิ่มเติมเกี่ยวกับคำสัญญาและสิ่งที่สังเกตได้ซึ่งเป็นวิธีใหม่กว่าในการทำ async stuffs นี้
เป็นปัญหาที่เราพบบ่อยมากในขณะที่ต้องดิ้นรนกับ 'ความลึกลับ' ของ JavaScript ให้ฉันลองทำความเข้าใจความลึกลับนี้ในวันนี้
เริ่มต้นด้วยฟังก์ชัน JavaScript ง่ายๆ:
function foo(){
// do something
return 'wohoo';
}
let bar = foo(); // bar is 'wohoo' here
นั่นคือการเรียกใช้ฟังก์ชันซิงโครนัสอย่างง่าย (โดยที่โค้ดแต่ละบรรทัดจะ 'เสร็จสิ้นงานแล้ว' ก่อนโค้ดถัดไปตามลำดับ) และผลลัพธ์ก็เป็นไปตามที่คาดไว้
ตอนนี้ขอเพิ่มการบิดเล็กน้อยโดยแนะนำการหน่วงเวลาเล็กน้อยในฟังก์ชันของเราเพื่อไม่ให้โค้ดทุกบรรทัด 'เสร็จสิ้น' ตามลำดับ ดังนั้นมันจะจำลองพฤติกรรมแบบอะซิงโครนัสของฟังก์ชัน:
function foo(){
setTimeout( ()=>{
return 'wohoo';
}, 1000 )
}
let bar = foo() // bar is undefined here
เอาล่ะความล่าช้านั้นทำให้ฟังก์ชั่นการใช้งานที่เราคาดหวังไม่สมบูรณ์! แต่เกิดอะไรขึ้นกันแน่? มันค่อนข้างสมเหตุสมผลถ้าคุณดูโค้ด ฟังก์ชันfoo()
เมื่อดำเนินการแล้วจะไม่คืนค่าอะไรเลย (ดังนั้นค่าที่ส่งคืนคือundefined
) แต่จะเริ่มตัวจับเวลาซึ่งเรียกใช้ฟังก์ชันหลังจาก 1 วินาทีเพื่อส่งคืน 'wohoo' แต่ในขณะที่คุณสามารถดูค่าที่กำหนดให้กับบาร์เป็นสิ่งที่กลับมาทันทีจาก foo () undefined
ซึ่งเป็นอะไรเช่นเพียง
แล้วเราจะจัดการกับปัญหานี้อย่างไร?
ลองถามฟังก์ชั่นของเราสำหรับPROMISE คำมั่นสัญญาเป็นเรื่องเกี่ยวกับความหมายนั่นหมายความว่าฟังก์ชันนี้รับประกันว่าคุณจะให้ผลลัพธ์ที่ได้รับในอนาคต ลองมาดูการดำเนินการสำหรับปัญหาเล็กน้อยของเราด้านบน:
function foo(){
return new Promise( (resolve, reject) => { // I want foo() to PROMISE me something
setTimeout ( function(){
// promise is RESOLVED , when execution reaches this line of code
resolve('wohoo')// After 1 second, RESOLVE the promise with value 'wohoo'
}, 1000 )
})
}
let bar ;
foo().then( res => {
bar = res;
console.log(bar) // will print 'wohoo'
});
ดังนั้นสรุปคือ - เพื่อจัดการกับฟังก์ชั่นอะซิงโครนัสเช่นการโทรตาม ajax เป็นต้นคุณสามารถใช้สัญญากับresolve
ค่า (ซึ่งคุณต้องการส่งคืน) ดังนั้นในระยะสั้นคุณแก้ไขค่าแทนที่จะส่งคืนในฟังก์ชันอะซิงโครนัส
UPDATE (สัญญากับ async / await)
นอกเหนือจากการใช้then/catch
เพื่อทำงานตามคำสัญญาแล้วยังมีอีกหนึ่งแนวทาง แนวคิดคือการรับรู้ฟังก์ชันอะซิงโครนัสจากนั้นรอให้คำสัญญาแก้ไขก่อนที่จะย้ายไปยังบรรทัดถัดไปของโค้ด มันยังคงเป็นเพียงส่วนpromises
ใต้ฝากระโปรง แต่มีวิธีการสังเคราะห์ที่แตกต่างออกไป เพื่อให้สิ่งต่างๆชัดเจนขึ้นคุณสามารถค้นหาการเปรียบเทียบด้านล่าง:
จากนั้น / จับเวอร์ชัน:
function saveUsers(){
getUsers()
.then(users => {
saveSomewhere(users);
})
.catch(err => {
console.error(err);
})
}
async / await เวอร์ชัน:
async function saveUsers(){
try{
let users = await getUsers()
saveSomewhere(users);
}
catch(err){
console.error(err);
}
}
อีกวิธีหนึ่งในการคืนค่าจากฟังก์ชันอะซิงโครนัสคือการส่งผ่านวัตถุที่จะเก็บผลลัพธ์จากฟังก์ชันอะซิงโครนัส
นี่คือตัวอย่างของสิ่งเดียวกัน:
var async = require("async");
// This wires up result back to the caller
var result = {};
var asyncTasks = [];
asyncTasks.push(function(_callback){
// some asynchronous operation
$.ajax({
url: '...',
success: function(response) {
result.response = response;
_callback();
}
});
});
async.parallel(asyncTasks, function(){
// result is available after performing asynchronous operation
console.log(result)
console.log('Done');
});
ฉันใช้result
วัตถุเพื่อเก็บค่าระหว่างการดำเนินการแบบอะซิงโครนัส สิ่งนี้ช่วยให้ผลลัพธ์พร้อมใช้งานแม้หลังจากงานอะซิงโครนัส
ฉันใช้แนวทางนี้มาก ฉันสนใจที่จะทราบว่าแนวทางนี้ทำงานได้ดีเพียงใดเมื่อมีการเชื่อมโยงผลลัพธ์กลับผ่านโมดูลที่ต่อเนื่องกัน
แม้ว่าคำสัญญาและการโทรกลับจะทำงานได้ดีในหลาย ๆ สถานการณ์ แต่ก็เป็นความเจ็บปวดที่จะต้องแสดงความรู้สึกเช่น:
if (!name) {
name = async1();
}
async2(name);
คุณจะจบลงไปผ่านasync1
; ตรวจสอบname
ว่าไม่ได้กำหนดหรือไม่และโทรกลับตามนั้น
async1(name, callback) {
if (name)
callback(name)
else {
doSomething(callback)
}
}
async1(name, async2)
แม้ว่าจะเป็นเรื่องปกติในตัวอย่างเล็ก ๆ แต่ก็น่ารำคาญเมื่อคุณมีกรณีที่คล้ายกันจำนวนมากและการจัดการข้อผิดพลาดที่เกี่ยวข้อง
Fibers
ช่วยในการแก้ปัญหา
var Fiber = require('fibers')
function async1(container) {
var current = Fiber.current
var result
doSomething(function(name) {
result = name
fiber.run()
})
Fiber.yield()
return result
}
Fiber(function() {
var name
if (!name) {
name = async1()
}
async2(name)
// Make any number of async calls from here
}
คุณสามารถเช็คเอาโครงการที่นี่
ตัวอย่างต่อไปนี้ที่ฉันเขียนแสดงวิธีการ
- จัดการการโทร HTTP แบบอะซิงโครนัส
- รอการตอบกลับจากการเรียก API แต่ละครั้ง
- ใช้รูปแบบสัญญา
- ใช้รูปแบบPromise.allเพื่อเข้าร่วมการโทร HTTP หลายสาย
ตัวอย่างการทำงานนี้มีอยู่ในตัว มันจะกำหนดออบเจ็กต์คำของ่ายๆที่ใช้XMLHttpRequest
อ็อบเจกต์หน้าต่างเพื่อโทรออก มันจะกำหนดฟังก์ชั่นง่ายๆเพื่อรอให้คำสัญญามากมายเสร็จสมบูรณ์
บริบท. ตัวอย่างกำลังค้นหาจุดสิ้นสุดของSpotify Web APIเพื่อค้นหาplaylist
วัตถุสำหรับชุดสตริงการสืบค้นที่กำหนด:
[
"search?type=playlist&q=%22doom%20metal%22",
"search?type=playlist&q=Adele"
]
สำหรับแต่ละรายการ Promise ใหม่จะเริ่มบล็อก - ExecutionBlock
แยกวิเคราะห์ผลลัพธ์กำหนดเวลาชุดของสัญญาใหม่ตามอาร์เรย์ผลลัพธ์ซึ่งเป็นรายการของuser
วัตถุSpotify และดำเนินการเรียก HTTP ใหม่ภายในExecutionProfileBlock
แบบอะซิงโครนัส
แล้วคุณจะเห็นโครงสร้างสัญญาซ้อนกันที่ช่วยให้คุณหลายวางไข่และไม่ตรงกันสมบูรณ์ซ้อนกันโทร HTTP Promise.all
และเข้าร่วมผลที่ได้จากส่วนย่อยของแต่ละสายผ่าน
หมายเหตุ Spotify search
API ล่าสุดจะต้องมีการระบุโทเค็นการเข้าถึงในส่วนหัวของคำขอ:
-H "Authorization: Bearer {your access token}"
ดังนั้นคุณต้องเรียกใช้ตัวอย่างต่อไปนี้คุณต้องใส่โทเค็นการเข้าถึงของคุณในส่วนหัวของคำขอ:
var spotifyAccessToken = "YourSpotifyAccessToken";
var console = {
log: function(s) {
document.getElementById("console").innerHTML += s + "<br/>"
}
}
// Simple XMLHttpRequest
// based on https://davidwalsh.name/xmlhttprequest
SimpleRequest = {
call: function(what, response) {
var request;
if (window.XMLHttpRequest) { // Mozilla, Safari, ...
request = new XMLHttpRequest();
} else if (window.ActiveXObject) { // Internet Explorer
try {
request = new ActiveXObject('Msxml2.XMLHTTP');
}
catch (e) {
try {
request = new ActiveXObject('Microsoft.XMLHTTP');
} catch (e) {}
}
}
// State changes
request.onreadystatechange = function() {
if (request.readyState === 4) { // Done
if (request.status === 200) { // Complete
response(request.responseText)
}
else
response();
}
}
request.open('GET', what, true);
request.setRequestHeader("Authorization", "Bearer " + spotifyAccessToken);
request.send(null);
}
}
//PromiseAll
var promiseAll = function(items, block, done, fail) {
var self = this;
var promises = [],
index = 0;
items.forEach(function(item) {
promises.push(function(item, i) {
return new Promise(function(resolve, reject) {
if (block) {
block.apply(this, [item, index, resolve, reject]);
}
});
}(item, ++index))
});
Promise.all(promises).then(function AcceptHandler(results) {
if (done) done(results);
}, function ErrorHandler(error) {
if (fail) fail(error);
});
}; //promiseAll
// LP: deferred execution block
var ExecutionBlock = function(item, index, resolve, reject) {
var url = "https://api.spotify.com/v1/"
url += item;
console.log( url )
SimpleRequest.call(url, function(result) {
if (result) {
var profileUrls = JSON.parse(result).playlists.items.map(function(item, index) {
return item.owner.href;
})
resolve(profileUrls);
}
else {
reject(new Error("call error"));
}
})
}
arr = [
"search?type=playlist&q=%22doom%20metal%22",
"search?type=playlist&q=Adele"
]
promiseAll(arr, function(item, index, resolve, reject) {
console.log("Making request [" + index + "]")
ExecutionBlock(item, index, resolve, reject);
}, function(results) { // Aggregated results
console.log("All profiles received " + results.length);
//console.log(JSON.stringify(results[0], null, 2));
///// promiseall again
var ExecutionProfileBlock = function(item, index, resolve, reject) {
SimpleRequest.call(item, function(result) {
if (result) {
var obj = JSON.parse(result);
resolve({
name: obj.display_name,
followers: obj.followers.total,
url: obj.href
});
} //result
})
} //ExecutionProfileBlock
promiseAll(results[0], function(item, index, resolve, reject) {
//console.log("Making request [" + index + "] " + item)
ExecutionProfileBlock(item, index, resolve, reject);
}, function(results) { // aggregated results
console.log("All response received " + results.length);
console.log(JSON.stringify(results, null, 2));
}
, function(error) { // Error
console.log(error);
})
/////
},
function(error) { // Error
console.log(error);
});
<div id="console" />
ผมได้กล่าวถึงอย่างกว้างขวางการแก้ปัญหานี้ที่นี่
คำตอบสั้น ๆ คือคุณต้องใช้การโทรกลับเช่นนี้:
function callback(response) {
// Here you can do what ever you want with the response object.
console.log(response);
}
$.ajax({
url: "...",
success: callback
});
2017 คำตอบ: ตอนนี้คุณสามารถทำสิ่งที่คุณต้องการได้ในทุกเบราว์เซอร์และโหนดปัจจุบัน
มันค่อนข้างง่าย:
- คืนคำสัญญา
- ใช้'await'ซึ่งจะบอกให้ JavaScript รอคำสัญญาที่จะแก้ไขเป็นค่า (เช่นการตอบสนอง HTTP)
- เพิ่มคีย์เวิร์ด'async'ในฟังก์ชันพาเรนต์
นี่คือโค้ดเวอร์ชันที่ใช้งานได้:
(async function(){
var response = await superagent.get('...')
console.log(response)
})()
await ได้รับการสนับสนุนในเบราว์เซอร์ปัจจุบันและโหนด 8 ทั้งหมด
Js เป็นเธรดเดียว
เบราว์เซอร์สามารถแบ่งออกเป็นสามส่วน:
1) ห่วงเหตุการณ์
2) Web API
3) คิวงาน
Event Loop จะทำงานตลอดไปเช่นชนิดของการวนซ้ำที่ไม่มีที่สิ้นสุด Event Queue คือการที่ฟังก์ชันทั้งหมดของคุณถูกผลักดันในบางเหตุการณ์ (ตัวอย่าง: คลิก) นี่คือการดำเนินการทีละคิวและใส่ลงใน Event loop ซึ่งเรียกใช้ฟังก์ชันนี้และเตรียมมันเอง สำหรับฟังก์ชันถัดไปหลังจากรันอันแรกซึ่งหมายความว่าการดำเนินการของฟังก์ชันหนึ่งจะไม่เริ่มทำงานจนกว่าฟังก์ชันก่อนที่จะดำเนินการในคิวในลูปเหตุการณ์
ตอนนี้ให้เราคิดว่าเราผลักสองฟังก์ชั่นในคิวหนึ่งคือการรับข้อมูลจากเซิร์ฟเวอร์และอีกอันใช้ข้อมูลนั้นเราพุชฟังก์ชัน serverRequest () ในคิวก่อนจากนั้นจึงใช้ฟังก์ชัน utiliseData () ฟังก์ชั่น serverRequest ไปในลูปเหตุการณ์และโทรไปยังเซิร์ฟเวอร์โดยที่เราไม่ทราบว่าจะต้องใช้เวลาเท่าไรในการรับข้อมูลจากเซิร์ฟเวอร์ดังนั้นกระบวนการนี้จึงคาดว่าจะใช้เวลาและเราจึงยุ่งกับการวนซ้ำเหตุการณ์ของเราดังนั้นจึงทำให้หน้าของเราค้างซึ่งเป็นที่ที่เว็บ API เข้ามามีบทบาทโดยใช้ฟังก์ชันนี้จากลูปเหตุการณ์และเกี่ยวข้องกับเซิร์ฟเวอร์ที่ทำให้การวนซ้ำของเหตุการณ์เป็นอิสระเพื่อให้เราสามารถเรียกใช้ฟังก์ชันถัดไปจากคิวได้ฟังก์ชันถัดไปในคิวคือ utiliseData () ซึ่งจะวนซ้ำ แต่เนื่องจากไม่มีข้อมูลจึงไป การเสียและการดำเนินการของฟังก์ชั่นถัดไปจะดำเนินต่อไปจนจบคิว (สิ่งนี้เรียกว่าการเรียก Async เช่นเราสามารถทำอย่างอื่นได้จนกว่าเราจะได้รับข้อมูล)
สมมติว่าฟังก์ชัน serverRequest () ของเรามีคำสั่งส่งคืนในรหัสเมื่อเราได้รับข้อมูลกลับจากเซิร์ฟเวอร์ Web API จะผลักดันให้อยู่ในคิวที่ท้ายคิว เนื่องจากถูกผลักไปที่ท้ายคิวเราจึงไม่สามารถใช้ข้อมูลของมันได้เนื่องจากไม่มีฟังก์ชันเหลืออยู่ในคิวของเราที่จะใช้ข้อมูลนี้ ดังนั้นจึงไม่สามารถคืนบางสิ่งจาก Async Call ได้
ดังนั้นวิธีแก้ปัญหานี้คือการเรียกกลับหรือสัญญา
ภาพจากหนึ่งในคำตอบที่นี่อธิบายการใช้การโทรกลับอย่างถูกต้อง ...เราให้ฟังก์ชันของเรา (ฟังก์ชันที่ใช้ข้อมูลที่ส่งคืนจากเซิร์ฟเวอร์) ไปยังเซิร์ฟเวอร์การเรียกใช้ฟังก์ชัน
function doAjax(callbackFunc, method, url) {
var xmlHttpReq = new XMLHttpRequest();
xmlHttpReq.open(method, url);
xmlHttpReq.onreadystatechange = function() {
if (xmlHttpReq.readyState == 4 && xmlHttpReq.status == 200) {
callbackFunc(xmlHttpReq.responseText);
}
}
xmlHttpReq.send(null);
}
ในรหัสของฉันเรียกว่าเป็น
function loadMyJson(categoryValue){
if(categoryValue==="veg")
doAjax(print,"GET","http://localhost:3004/vegetables");
else if(categoryValue==="fruits")
doAjax(print,"GET","http://localhost:3004/fruits");
else
console.log("Data not found");
}
การโทรกลับ Javscript.info
คุณสามารถใช้ไลบรารีที่กำหนดเองนี้ (เขียนโดยใช้ Promise) เพื่อโทรทางไกล
function $http(apiConfig) {
return new Promise(function (resolve, reject) {
var client = new XMLHttpRequest();
client.open(apiConfig.method, apiConfig.url);
client.send();
client.onload = function () {
if (this.status >= 200 && this.status < 300) {
// Performs the function "resolve" when this.status is equal to 2xx.
// Your logic here.
resolve(this.response);
}
else {
// Performs the function "reject" when this.status is different than 2xx.
reject(this.statusText);
}
};
client.onerror = function () {
reject(this.statusText);
};
});
}
ตัวอย่างการใช้งานง่ายๆ:
$http({
method: 'get',
url: 'google.com'
}).then(function(response) {
console.log(response);
}, function(error) {
console.log(error)
});
ทางออกก็คือการดำเนินการรหัสผ่านผู้บริหารลำดับnsynjs
หากมีการยืนยันฟังก์ชันพื้นฐาน
nsynjs จะประเมินสัญญาทั้งหมดตามลำดับและใส่ผลลัพธ์ของสัญญาลงในdata
ทรัพย์สิน:
function synchronousCode() {
var getURL = function(url) {
return window.fetch(url).data.text().data;
};
var url = 'https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js';
console.log('received bytes:',getURL(url).length);
};
nsynjs.run(synchronousCode,{},function(){
console.log('synchronousCode done');
});
<script src="https://rawgit.com/amaksr/nsynjs/master/nsynjs.js"></script>
หากฟังก์ชันพื้นฐานไม่ได้รับการรับรอง
ขั้นตอนที่ 1. ตัดฟังก์ชั่นด้วยการโทรกลับไปยัง nsynjs-awareness wrapper (หากมีเวอร์ชันที่สัญญาไว้คุณสามารถข้ามขั้นตอนนี้ได้):
var ajaxGet = function (ctx,url) {
var res = {};
var ex;
$.ajax(url)
.done(function (data) {
res.data = data;
})
.fail(function(e) {
ex = e;
})
.always(function() {
ctx.resume(ex);
});
return res;
};
ajaxGet.nsynjsHasCallback = true;
ขั้นตอนที่ 2. ใส่ตรรกะซิงโครนัสลงในฟังก์ชัน:
function process() {
console.log('got data:', ajaxGet(nsynjsCtx, "data/file1.json").data);
}
ขั้นตอนที่ 3. เรียกใช้ฟังก์ชันในลักษณะซิงโครนัสผ่าน nsynjs:
nsynjs.run(process,this,function () {
console.log("synchronous function finished");
});
Nsynjs จะประเมินตัวดำเนินการและนิพจน์ทั้งหมดทีละขั้นตอนโดยหยุดการดำเนินการชั่วคราวในกรณีที่ผลลัพธ์ของฟังก์ชันช้าบางฟังก์ชันไม่พร้อมใช้งาน
ตัวอย่างเพิ่มเติมที่นี่: https://github.com/amaksr/nsynjs/tree/master/examples
ECMAScript 6 มี 'เครื่องกำเนิดไฟฟ้า' ซึ่งช่วยให้คุณสามารถตั้งโปรแกรมในรูปแบบอะซิงโครนัสได้อย่างง่ายดาย
function* myGenerator() {
const callback = yield;
let [response] = yield $.ajax("https://stackoverflow.com", {complete: callback});
console.log("response is:", response);
// examples of other things you can do
yield setTimeout(callback, 1000);
console.log("it delayed for 1000ms");
while (response.statusText === "error") {
[response] = yield* anotherGenerator();
}
}
ในการเรียกใช้รหัสด้านบนคุณต้องทำสิ่งนี้:
const gen = myGenerator(); // Create generator
gen.next(); // Start it
gen.next((...args) => gen.next([...args])); // Set its callback function
หากคุณต้องการกำหนดเป้าหมายเบราว์เซอร์ที่ไม่รองรับ ES6 คุณสามารถรันโค้ดผ่าน Babel หรือ closed-compiler เพื่อสร้าง ECMAScript 5
การเรียกกลับ...args
จะถูกรวมไว้ในอาร์เรย์และถูกทำลายเมื่อคุณอ่านเพื่อให้รูปแบบสามารถรับมือกับการเรียกกลับที่มีหลายอาร์กิวเมนต์ได้ ตัวอย่างเช่นกับโหนด fs :
const [err, data] = yield fs.readFile(filePath, "utf-8", callback);
ต่อไปนี้เป็นแนวทางบางประการในการทำงานกับคำขอแบบอะซิงโครนัส:
- วัตถุสัญญาของเบราว์เซอร์
- ถาม - ไลบรารีสัญญาสำหรับ JavaScript
- A + สัญญา js
- jQuery รอการตัดบัญชี
- XMLHttpRequest API
- การใช้แนวคิดการโทรกลับ - เป็นการใช้งานในคำตอบแรก
ตัวอย่าง: jQuery รอการใช้งานเพื่อทำงานกับคำขอหลายรายการ
var App = App || {};
App = {
getDataFromServer: function(){
var self = this,
deferred = $.Deferred(), requests = []; requests.push($.getJSON('request/ajax/url/1'));
requests.push($.getJSON('request/ajax/url/2')); $.when.apply(jQuery, requests).done(function(xhrResponse) {
return deferred.resolve(xhrResponse.result);
});
return deferred;
},
init: function(){
this.getDataFromServer().done(_.bind(function(resp1, resp2) {
// Do the operations which you wanted to do when you
// get a response from Ajax, for example, log response.
}, this));
}
};
App.init();
เราพบว่าตัวเองอยู่ในจักรวาลซึ่งดูเหมือนจะก้าวหน้าไปตามมิติที่เราเรียกว่า "เวลา" เราไม่เข้าใจจริงๆว่าเวลาคืออะไร แต่เราได้พัฒนานามธรรมและคำศัพท์ที่ช่วยให้เราสามารถหาเหตุผลและพูดถึงเรื่องนี้: "อดีต", "ปัจจุบัน", "อนาคต", "ก่อน", "หลัง"
ระบบคอมพิวเตอร์ที่เราสร้างขึ้น - มากขึ้นเรื่อย ๆ - มีเวลาเป็นมิติที่สำคัญ บางสิ่งถูกกำหนดให้เกิดขึ้นในอนาคต จากนั้นสิ่งอื่น ๆ จะต้องเกิดขึ้นหลังจากที่สิ่งแรกเหล่านั้นเกิดขึ้นในที่สุด นี่คือแนวคิดพื้นฐานที่เรียกว่า "asynchronicity" ในโลกที่มีเครือข่ายมากขึ้นเรื่อย ๆ กรณีที่พบบ่อยที่สุดของ asynchronicity กำลังรอให้ระบบระยะไกลตอบสนองคำขอบางอย่าง
ลองพิจารณาตัวอย่าง คุณโทรหาคนส่งนมและสั่งนม เมื่อได้มาแล้วคุณต้องการใส่ลงในกาแฟของคุณ คุณไม่สามารถใส่นมลงในกาแฟได้ในขณะนี้เนื่องจากยังไม่ได้ใส่นม คุณต้องรอให้มันมาก่อนที่จะใส่ลงในกาแฟของคุณ กล่าวอีกนัยหนึ่งสิ่งต่อไปนี้จะใช้ไม่ได้:
var milk = order_milk();
put_in_coffee(milk);
เพราะ JS มีวิธีการที่จะรู้ว่ามันต้องไม่รอสำหรับการให้เสร็จก่อนที่จะดำเนินการorder_milk
put_in_coffee
กล่าวอีกนัยหนึ่งก็คือไม่ทราบว่าorder_milk
เป็นแบบอะซิงโครนัส - เป็นสิ่งที่จะไม่ส่งผลให้เกิดน้ำนมจนกว่าจะถึงเวลาในอนาคต JS และภาษาประกาศอื่น ๆ ดำเนินการคำสั่งหนึ่งคำสั่งโดยไม่ต้องรอ
วิธี JS แบบคลาสสิกสำหรับปัญหานี้โดยใช้ประโยชน์จากข้อเท็จจริงที่ว่า JS สนับสนุนฟังก์ชันเป็นออบเจ็กต์ชั้นหนึ่งซึ่งสามารถส่งผ่านไปมาได้คือการส่งผ่านฟังก์ชันเป็นพารามิเตอร์ไปยังคำขอแบบอะซิงโครนัสซึ่งจะเรียกใช้เมื่อดำเนินการเสร็จสิ้น งานของมันในอนาคต นั่นคือวิธีการ "โทรกลับ" ดูเหมือนว่า:
order_milk(put_in_coffee);
order_milk
kicks put_in_coffee
ปิดคำสั่งซื้อนมแล้วเมื่อและเมื่อมันมาถึงมันจะเรียก
ปัญหาเกี่ยวกับวิธีการเรียกกลับนี้คือมันก่อให้เกิดมลพิษต่อความหมายปกติของฟังก์ชันที่รายงานผลลัพธ์ด้วยreturn
; ฟังก์ชันจะต้องไม่รายงานผลลัพธ์โดยการเรียกการเรียกกลับที่กำหนดให้เป็นพารามิเตอร์แทน นอกจากนี้วิธีนี้อาจกลายเป็นเรื่องยุ่งยากอย่างรวดเร็วเมื่อต้องรับมือกับลำดับเหตุการณ์ที่ยาวขึ้น ตัวอย่างเช่นสมมติว่าฉันต้องการรอให้ใส่นมลงในกาแฟจากนั้นจึงทำขั้นตอนที่สามนั่นคือการดื่มกาแฟ ฉันต้องเขียนอะไรแบบนี้:
order_milk(function(milk) { put_in_coffee(milk, drink_coffee); }
ที่ฉันส่งผ่านไปput_in_coffee
ยังนมทั้งสองเพื่อใส่ลงไปและการดำเนินการ ( drink_coffee
) เพื่อดำเนินการเมื่อใส่นมเข้าไปโค้ดดังกล่าวเขียนและอ่านและดีบักได้ยาก
ในกรณีนี้เราสามารถเขียนโค้ดใหม่ในคำถามเป็น:
var answer;
$.ajax('/foo.json') . done(function(response) {
callback(response.data);
});
function callback(data) {
console.log(data);
}
ใส่คำสัญญา
นี่เป็นแรงจูงใจสำหรับแนวคิดเรื่อง "คำสัญญา" ซึ่งเป็นค่านิยมประเภทหนึ่งซึ่งแสดงถึงผลลัพธ์ในอนาคตหรือแบบอะซิงโครนัสของบางประเภท อาจเป็นตัวแทนของสิ่งที่เกิดขึ้นแล้วหรือกำลังจะเกิดขึ้นในอนาคตหรืออาจไม่เคยเกิดขึ้นเลย คำสัญญามีวิธีการเดียวที่ตั้งชื่อthen
เพื่อให้คุณดำเนินการเพื่อดำเนินการเมื่อผลลัพธ์ที่สัญญาแสดงถึงได้รับรู้แล้ว
ในกรณีของนมและกาแฟของเราเราออกแบบorder_milk
เพื่อคืนสัญญาสำหรับนมที่จะมาถึงจากนั้นระบุput_in_coffee
เป็นการthen
กระทำดังนี้:
order_milk() . then(put_in_coffee)
ข้อดีอย่างหนึ่งของสิ่งนี้คือเราสามารถรวมสิ่งเหล่านี้เข้าด้วยกันเพื่อสร้างลำดับของเหตุการณ์ในอนาคต ("การผูกมัด"):
order_milk() . then(put_in_coffee) . then(drink_coffee)
ขอสัญญากับปัญหาเฉพาะของคุณ เราจะรวมตรรกะคำขอของเราไว้ในฟังก์ชันซึ่งจะส่งคืนคำสัญญา:
function get_data() {
return $.ajax('/foo.json');
}
ที่จริงแล้วทุกสิ่งที่เราได้ทำคือการเพิ่มการเรียกร้องให้return
$.ajax
สิ่งนี้ได้ผลเพราะ jQuery $.ajax
ส่งคืนสิ่งที่เหมือนคำสัญญาอยู่แล้ว (ในทางปฏิบัติโดยไม่ต้องลงรายละเอียดเราต้องการที่จะปิดการโทรนี้เพื่อคืนสัญญาที่แท้จริงหรือใช้ทางเลือกอื่นแทน$.ajax
) ตอนนี้หากเราต้องการโหลดไฟล์และรอให้เสร็จสิ้นและ แล้วทำอะไรบางอย่างเราสามารถพูดได้ง่ายๆ
get_data() . then(do_something)
ตัวอย่างเช่น
get_data() .
then(function(data) { console.log(data); });
เมื่อใช้คำสัญญาเราจะส่งผ่านฟังก์ชั่นมากมายเข้ามาthen
ดังนั้นการใช้ฟังก์ชันลูกศรสไตล์ ES6 ที่กะทัดรัดกว่าจึงเป็นประโยชน์:
get_data() .
then(data => console.log(data));
async
คำหลัก
แต่ยังมีบางอย่างที่ไม่พึงพอใจอย่างคลุมเครือเกี่ยวกับการต้องเขียนโค้ดทางเดียวหากซิงโครนัสและวิธีที่แตกต่างกันมากหากอะซิงโครนัส สำหรับซิงโครนัสเราเขียน
a();
b();
แต่ถ้าa
เป็นแบบอะซิงโครนัสพร้อมกับสัญญาที่เราต้องเขียน
a() . then(b);
ข้างต้นเรากล่าวว่า "JS ไม่มีทางรู้ว่าต้องรอให้การเรียกครั้งแรกเสร็จสิ้นก่อนที่จะดำเนินการครั้งที่สอง" มันจะไม่ดีถ้ามีเป็นวิธีที่จะบอกว่าบาง JS? ปรากฎว่ามี - await
คำหลักที่ใช้ภายในฟังก์ชันชนิดพิเศษที่เรียกว่าฟังก์ชัน "async" คุณลักษณะนี้เป็นส่วนหนึ่งของ ES เวอร์ชันที่กำลังจะมาถึง แต่มีให้ใช้งานแล้วในทรานสไพเลอร์เช่น Babel เนื่องจากค่าที่ตั้งไว้ล่วงหน้า สิ่งนี้ช่วยให้เราสามารถเขียนได้
async function morning_routine() {
var milk = await order_milk();
var coffee = await put_in_coffee(milk);
await drink(coffee);
}
ในกรณีของคุณคุณจะสามารถเขียนสิ่งที่ต้องการได้
async function foo() {
data = await get_data();
console.log(data);
}
คำตอบสั้น ๆ : foo()
วิธีการของคุณจะส่งกลับทันทีในขณะที่การ$ajax()
โทรดำเนินการแบบอะซิงโครนัสหลังจากที่ฟังก์ชันกลับมา ปัญหาคือจะจัดเก็บผลลัพธ์ที่เรียกโดย async call ได้อย่างไรเมื่อส่งกลับ
มีวิธีแก้ปัญหาหลายอย่างในชุดข้อความนี้ บางทีวิธีที่ง่ายที่สุดคือส่งอ็อบเจกต์ไปยังfoo()
เมธอดและเก็บผลลัพธ์ไว้ในสมาชิกของอ็อบเจ็กต์นั้นหลังจากการเรียก async เสร็จสิ้น
function foo(result) {
$.ajax({
url: '...',
success: function(response) {
result.response = response; // Store the async result
}
});
}
var result = { response: null }; // Object to hold the async result
foo(result); // Returns before the async completes
โปรดทราบว่าการโทรไปfoo()
ยังจะไม่ส่งคืนสิ่งที่เป็นประโยชน์ อย่างไรก็ตามผลลัพธ์ของการโทรแบบ async จะถูกเก็บไว้ในresult.response
ไฟล์.
ใช้callback()
ฟังก์ชันภายในfoo()
ความสำเร็จ ลองใช้วิธีนี้ เป็นเรื่องง่ายและเข้าใจง่าย
var lat = "";
var lon = "";
function callback(data) {
lat = data.lat;
lon = data.lon;
}
function getLoc() {
var url = "http://ip-api.com/json"
$.getJSON(url, function(data) {
callback(data);
});
}
getLoc();
การใช้คำสัญญา
Promise
คำตอบที่สมบูรณ์แบบที่สุดสำหรับคำถามนี้คือการใช้
function ajax(method, url, params) {
return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.onload = function() {
resolve(this.responseText);
};
xhr.onerror = reject;
xhr.open(method, url);
xhr.send(params);
});
}
การใช้งาน
ajax("GET", "/test", "acrive=1").then(function(result) {
// Code depending on result
})
.catch(function() {
// An error occurred
});
แต่เดี๋ยวก่อน...!
มีปัญหากับการใช้คำสัญญา!
เหตุใดเราจึงควรใช้คำสัญญาที่กำหนดขึ้นเอง?
ฉันใช้วิธีแก้ปัญหานี้มาระยะหนึ่งจนกระทั่งพบว่ามีข้อผิดพลาดในเบราว์เซอร์เก่า:
Uncaught ReferenceError: Promise is not defined
ดังนั้นฉันจึงตัดสินใจใช้คลาส Promise ของตัวเองสำหรับES3 ให้ต่ำกว่าคอมไพเลอร์ js หากไม่ได้กำหนดไว้ เพียงเพิ่มรหัสนี้ก่อนรหัสหลักของคุณจากนั้นใช้ Promise อย่างปลอดภัย!
if(typeof Promise === "undefined"){
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
var Promise = function () {
function Promise(main) {
var _this = this;
_classCallCheck(this, Promise);
this.value = undefined;
this.callbacks = [];
var resolve = function resolve(resolveValue) {
_this.value = resolveValue;
_this.triggerCallbacks();
};
var reject = function reject(rejectValue) {
_this.value = rejectValue;
_this.triggerCallbacks();
};
main(resolve, reject);
}
Promise.prototype.then = function then(cb) {
var _this2 = this;
var next = new Promise(function (resolve) {
_this2.callbacks.push(function (x) {
return resolve(cb(x));
});
});
return next;
};
Promise.prototype.catch = function catch_(cb) {
var _this2 = this;
var next = new Promise(function (reject) {
_this2.callbacks.push(function (x) {
return reject(cb(x));
});
});
return next;
};
Promise.prototype.triggerCallbacks = function triggerCallbacks() {
var _this3 = this;
this.callbacks.forEach(function (cb) {
cb(_this3.value);
});
};
return Promise;
}();
}
คำถามคือ:
ฉันจะตอบกลับการตอบกลับจากการโทรแบบอะซิงโครนัสได้อย่างไร
ซึ่งสามารถตีความได้ว่า:
จะทำให้โค้ดอะซิงโครนัสดูซิงโครนัสได้อย่างไร?
การแก้ปัญหาจะได้รับการหลีกเลี่ยงการเรียกกลับและใช้การรวมกันของสัญญาและasync / รอคอย
ผมขอยกตัวอย่างคำขอ Ajax
(แม้ว่าจะสามารถเขียนด้วย Javascript ได้ แต่ฉันก็ชอบที่จะเขียนใน Python และรวบรวมเป็น Javascript โดยใช้Transcryptมันจะชัดเจนพอ)
ให้เปิดใช้งานการใช้งาน JQuery ก่อนเพื่อ$
ให้พร้อมใช้งานเป็นS
:
__pragma__ ('alias', 'S', '$')
กำหนดฟังก์ชันที่ส่งคืนPromiseในกรณีนี้คือการเรียก Ajax:
def read(url: str):
deferred = S.Deferred()
S.ajax({'type': "POST", 'url': url, 'data': { },
'success': lambda d: deferred.resolve(d),
'error': lambda e: deferred.reject(e)
})
return deferred.promise()
ใช้รหัสอะซิงโครนัสราวกับว่ามันเป็นซิงโครนัส :
async def readALot():
try:
result1 = await read("url_1")
result2 = await read("url_2")
except Exception:
console.warn("Reading a lot failed")
แน่นอนว่ามีหลายวิธีเช่นคำขอแบบซิงโครนัสสัญญา แต่จากประสบการณ์ของฉันฉันคิดว่าคุณควรใช้วิธีการโทรกลับ เป็นเรื่องปกติที่พฤติกรรมแบบอะซิงโครนัสของ Javascript ดังนั้นข้อมูลโค้ดของคุณสามารถเขียนใหม่ได้แตกต่างกันเล็กน้อย:
function foo() {
var result;
$.ajax({
url: '...',
success: function(response) {
myCallback(response);
}
});
return result;
}
function myCallback(response) {
// Does something.
}
หลังจากอ่านคำตอบทั้งหมดที่นี่และจากประสบการณ์ของฉันฉันต้องการที่จะดำเนินการต่อรายละเอียดของcallback, promise and async/await
การเขียนโปรแกรมแบบอะซิงโครนัสใน JavaScript
1) การโทรกลับ:เหตุผลพื้นฐานสำหรับการโทรกลับคือการเรียกใช้โค้ดเพื่อตอบสนองต่อเหตุการณ์ (ดูตัวอย่างด้านล่าง) เราใช้การโทรกลับใน JavaScript ทุกครั้ง
const body = document.getElementsByTagName('body')[0];
function callback() {
console.log('Hello');
}
body.addEventListener('click', callback);
แต่ถ้าคุณต้องใช้การเรียกกลับที่ซ้อนกันหลายรายการในตัวอย่างด้านล่างมันจะแย่มากสำหรับการปรับโครงสร้างโค้ด
asyncCallOne(function callback1() {
asyncCallTwo(function callback2() {
asyncCallThree(function callback3() {
...
})
})
})
2) Promise:ไวยากรณ์ ES6 - สัญญาแก้ปัญหาการเรียกกลับนรก!
const myFirstPromise = new Promise((resolve, reject) => {
// We call resolve(...) when what we were doing asynchronously was successful, and reject(...) when it failed.
// In this example, we use setTimeout(...) to simulate async code.
// In reality, you will probably be using something like XHR request or an HTML5 API.
setTimeout(() => {
resolve("Success!") // Yay! Everything went well!
}, 250)
})
myFirstPromise
.then((res) => {
return res.json();
})
.then((data) => {
console.log(data);
})
.catch((e) => {
console.log(e);
});
myFirstPromise เป็นอินสแตนซ์ Promise ที่แสดงถึงกระบวนการของรหัส async ฟังก์ชันแก้ไขจะส่งสัญญาณว่าอินสแตนซ์ Promise เสร็จสิ้นแล้ว หลังจากนั้นเราสามารถเรียก. then () (chain of. แล้วตามที่คุณต้องการ) และ .catch () บนอินสแตนซ์คำสัญญา:
then — Runs a callback you pass to it when the promise has fulfilled.
catch — Runs a callback you pass to it when something went wrong.
3) Async / Await:ไวยากรณ์ใหม่ ES6 - Await เป็นไวยากรณ์น้ำตาลของ Promise!
ฟังก์ชัน Async ช่วยให้เรามีไวยากรณ์ที่ชัดเจนและกระชับซึ่งช่วยให้เราเขียนโค้ดน้อยลงเพื่อให้ได้ผลลัพธ์แบบเดียวกับที่เราจะได้รับตามคำสัญญา Async / Await มีลักษณะคล้ายกับรหัสซิงโครนัสและรหัสซิงโครนัสนั้นอ่านและเขียนได้ง่ายกว่ามาก ที่จะจับข้อผิดพลาดกับ Async / try...catch
รอเราสามารถใช้บล็อก ที่นี่คุณไม่จำเป็นต้องเขียนโซ่ของ. then () ของไวยากรณ์ Promise
const getExchangeRate = async () => {
try {
const res = await fetch('https://getExchangeRateData');
const data = await res.json();
console.log(data);
} catch (err) {
console.error(err);
}
}
getExchangeRate();
สรุป: นี่คือไวยากรณ์ทั้งสามสำหรับการเขียนโปรแกรมแบบอะซิงโครนัสใน JavaScript ที่คุณเข้าใจดี ดังนั้นถ้าเป็นไปได้ขอแนะนำว่าคุณควรใช้ "สัญญา" หรือ "async / await" ในการปรับโครงสร้างรหัสอะซิงโครนัสของคุณใหม่ (ส่วนใหญ่เป็นคำขอ XHR) !
แทนที่จะโยนโค้ดใส่คุณมี 2 แนวคิดที่เป็นกุญแจสำคัญในการทำความเข้าใจว่า JS จัดการกับการโทรกลับและความไม่สัมพันธ์กันอย่างไร (นั่นเป็นคำหรือเปล่า?)
ห่วงเหตุการณ์และแบบจำลองการทำงานพร้อมกัน
มีสามสิ่งที่คุณต้องระวัง คิว; ลูปเหตุการณ์และสแต็ก
ในแง่กว้าง ๆ ที่เข้าใจง่ายเหตุการณ์วนซ้ำก็เหมือนกับตัวจัดการโปรเจ็กต์คือฟังก์ชั่นต่างๆที่ต้องการเรียกใช้และสื่อสารระหว่างคิวและสแต็กอยู่ตลอดเวลา
while (queue.waitForMessage()) {
queue.processNextMessage();
}
เมื่อได้รับข้อความให้เรียกใช้บางสิ่งก็จะเพิ่มลงในคิว คิวคือรายการของสิ่งที่รอดำเนินการ (เช่นคำขอ AJAX ของคุณ) ลองนึกภาพแบบนี้:
1. call foo.com/api/bar using foobarFunc
2. Go perform an infinite loop
... and so on
เมื่อหนึ่งในข้อความเหล่านี้กำลังจะดำเนินการข้อความนั้นจะปรากฏข้อความจากคิวและสร้างสแตกสแต็กคือทุกสิ่งที่ JS ต้องดำเนินการเพื่อดำเนินการตามคำสั่งในข้อความ ดังนั้นในตัวอย่างของเรามีการบอกให้โทรfoobarFunc
function foobarFunc (var) {
console.log(anotherFunction(var));
}
ดังนั้นสิ่งใดก็ตามที่ foobarFunc จำเป็นต้องดำเนินการ (ในกรณีของเราanotherFunction
) จะถูกผลักไปที่สแต็ก ดำเนินการแล้วลืมเกี่ยวกับ - ลูปเหตุการณ์จะย้ายไปยังสิ่งถัดไปในคิว (หรือฟังข้อความ)
สิ่งสำคัญที่นี่คือลำดับของการดำเนินการ นั่นคือ
เมื่อมีบางสิ่งกำลังจะทำงาน
เมื่อคุณโทรโดยใช้ AJAX ไปยังบุคคลภายนอกหรือเรียกใช้รหัสอะซิงโครนัส (เช่น setTimeout) Javascript จะขึ้นอยู่กับการตอบสนองก่อนที่จะดำเนินการต่อ
คำถามใหญ่คือเมื่อไหร่จะได้รับคำตอบ? คำตอบคือเราไม่รู้ - ดังนั้นเหตุการณ์ที่วนซ้ำกำลังรอให้ข้อความนั้นบอกว่า "เดี๋ยวก่อนเรียกฉัน" หาก JS รอข้อความนั้นพร้อมกันแอปของคุณจะหยุดทำงานและจะดูด ดังนั้น JS จึงดำเนินการรายการถัดไปในคิวในขณะที่รอให้ข้อความถูกเพิ่มกลับไปที่คิว
นั่นเป็นเหตุผลที่มีการทำงานไม่ตรงกันเราจะใช้สิ่งที่เรียกว่าการเรียกกลับ มันเหมือนกับคำสัญญาทีเดียว ในขณะที่ฉันสัญญาว่าจะคืนบางสิ่งบางอย่างในบางจุด jQuery ใช้การเรียกกลับเฉพาะที่เรียกว่าdeffered.done
deffered.fail
และdeffered.always
(ท่ามกลางคนอื่น ๆ ) คุณสามารถดูทั้งหมดได้ที่นี่
ดังนั้นสิ่งที่คุณต้องทำคือส่งผ่านฟังก์ชันที่สัญญาว่าจะดำเนินการในบางจุดด้วยข้อมูลที่ส่งผ่านไป
เนื่องจากการโทรกลับไม่ได้ดำเนินการในทันที แต่ในเวลาต่อมาสิ่งสำคัญคือต้องส่งต่อการอ้างอิงไปยังฟังก์ชันไม่ให้ดำเนินการ ดังนั้น
function foo(bla) {
console.log(bla)
}
ดังนั้นเวลาส่วนใหญ่ (แต่ไม่เสมอไป) คุณจะfoo
ไม่ผ่านfoo()
หวังว่าจะสมเหตุสมผลบ้าง เมื่อคุณพบสิ่งต่างๆเช่นนี้ที่ดูสับสน - ฉันขอแนะนำอย่างยิ่งให้อ่านเอกสารอย่างละเอียดเพื่ออย่างน้อยก็ทำความเข้าใจกับมัน มันจะทำให้คุณเป็นนักพัฒนาที่ดีขึ้นมาก