Làm cách nào để trả lại phản hồi từ cuộc gọi không đồng bộ?
Tôi có một hàm foo
tạo yêu cầu không đồng bộ. Làm cách nào để trả lại phản hồi / kết quả từ đó foo
?
Tôi đã thử trả lại giá trị từ lệnh gọi lại, cũng như gán kết quả cho biến cục bộ bên trong hàm và trả về biến đó, nhưng không có cách nào trong số đó thực sự trả về phản hồi (tất cả đều trả về undefined
hoặc bất kể giá trị ban đầu của biến result
là gì) .
Ví dụ sử dụng ajax
hàm của 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`
}
Ví dụ sử dụng 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`
}
Ví dụ sử dụng then
khối của một lời hứa:
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`
}
Trả lời
→ Để có giải thích chung hơn về hành vi không đồng bộ với các ví dụ khác nhau, vui lòng xem Tại sao biến của tôi không bị thay đổi sau khi tôi sửa đổi nó bên trong một hàm? - Tham chiếu mã không đồng bộ
→ Nếu bạn đã hiểu vấn đề, hãy chuyển đến các giải pháp khả thi bên dưới.
Vấn đề
Các Một trong Ajax là viết tắt của Asynchronous . Điều đó có nghĩa là việc gửi yêu cầu (hoặc đúng hơn là nhận phản hồi) được đưa ra khỏi quy trình thực thi thông thường. Trong ví dụ của bạn, $.ajax
trả về ngay lập tức và câu lệnh tiếp theo return result;
, được thực thi trước khi hàm bạn đã chuyển dưới dạng success
gọi lại thậm chí được gọi.
Đây là một phép tương tự hy vọng làm cho sự khác biệt giữa luồng đồng bộ và không đồng bộ rõ ràng hơn:
Đồng bộ
Hãy tưởng tượng bạn gọi điện cho một người bạn và yêu cầu anh ấy tìm kiếm thông tin gì đó cho bạn. Mặc dù có thể mất một chút thời gian, nhưng bạn hãy đợi điện thoại và nhìn chằm chằm vào không gian, cho đến khi bạn của bạn đưa ra câu trả lời mà bạn cần.
Điều tương tự cũng xảy ra khi bạn thực hiện một lệnh gọi hàm chứa mã "bình thường":
function findItem() {
var item;
while(item_not_found) {
// search
}
return item;
}
var item = findItem();
// Do something with item
doSomethingElse();
Mặc dù findItem
có thể mất nhiều thời gian để thực thi, bất kỳ mã nào đến sau var item = findItem();
phải đợi cho đến khi hàm trả về kết quả.
Không đồng bộ
Bạn gọi lại cho bạn mình vì lý do tương tự. Nhưng lần này bạn nói với anh ấy rằng bạn đang vội và anh ấy nên gọi lại cho bạn qua điện thoại di động. Bạn gác máy, rời khỏi nhà và làm bất cứ điều gì bạn định làm. Khi bạn của bạn gọi lại cho bạn, bạn đang xử lý thông tin mà anh ấy đã cung cấp cho bạn.
Đó chính xác là những gì đang xảy ra khi bạn thực hiện một yêu cầu Ajax.
findItem(function(item) {
// Do something with the item
});
doSomethingElse();
Thay vì đợi phản hồi, việc thực thi tiếp tục ngay lập tức và câu lệnh sau khi lệnh gọi Ajax được thực hiện. Để cuối cùng nhận được phản hồi, bạn cung cấp một hàm sẽ được gọi khi nhận được phản hồi, một lệnh gọi lại (thông báo điều gì? Gọi lại ?). Bất kỳ câu lệnh nào đến sau cuộc gọi đó được thực hiện trước khi lệnh gọi lại được gọi.
Các giải pháp)
Nắm bắt bản chất không đồng bộ của JavaScript! Mặc dù các hoạt động không đồng bộ nhất định cung cấp các đối tác đồng bộ ("Ajax" cũng vậy), nhưng thường không khuyến khích sử dụng chúng, đặc biệt là trong ngữ cảnh trình duyệt.
Bạn hỏi tại sao nó xấu?
JavaScript chạy trong chuỗi giao diện người dùng của trình duyệt và bất kỳ quá trình chạy dài nào sẽ khóa giao diện người dùng, khiến nó không phản hồi. Ngoài ra, có một giới hạn trên về thời gian thực thi JavaScript và trình duyệt sẽ hỏi người dùng có tiếp tục thực thi hay không.
Tất cả những điều này là một trải nghiệm người dùng thực sự tồi tệ. Người dùng sẽ không thể biết liệu mọi thứ có hoạt động tốt hay không. Hơn nữa, ảnh hưởng sẽ tồi tệ hơn đối với người dùng có kết nối chậm.
Trong phần sau, chúng ta sẽ xem xét ba giải pháp khác nhau, tất cả đều được xây dựng dựa trên nhau:
- Hứa hẹn với
async/await
(ES2017 +, có sẵn trong các trình duyệt cũ hơn nếu bạn sử dụng trình chuyển đổi hoặc trình tái tạo) - Gọi lại (phổ biến trong nút)
- Hứa hẹn với
then()
(ES2015 +, khả dụng trong các trình duyệt cũ hơn nếu bạn sử dụng một trong nhiều thư viện hứa hẹn)
Cả ba đều có sẵn trong các trình duyệt hiện tại và nút 7+.
ES2017 +: Hứa hẹn với async/await
Phiên bản ECMAScript được phát hành vào năm 2017 đã giới thiệu hỗ trợ mức cú pháp cho các hàm không đồng bộ. Với sự trợ giúp của async
và await
, bạn có thể viết không đồng bộ theo "kiểu đồng bộ". Mã vẫn không đồng bộ, nhưng dễ đọc / hiểu hơn.
async/await
xây dựng dựa trên các lời hứa: một async
hàm luôn trả về một lời hứa. await
"mở ra" một lời hứa và kết quả là giá trị mà lời hứa đã được giải quyết hoặc tạo ra một lỗi nếu lời hứa bị từ chối.
Quan trọng: Bạn chỉ có thể sử dụng await
bên trong một async
hàm. Hiện tại, cấp cao nhất await
chưa được hỗ trợ, vì vậy bạn có thể phải tạo IIFE không đồng bộ ( Biểu thức hàm được gọi ngay lập tức ) để bắt đầu một async
ngữ cảnh.
Bạn có thể đọc thêm về async
và await
trên MDN.
Dưới đây là một ví dụ dựa trên sự chậm trễ ở trên:
// 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);
})();
Hỗ trợ các phiên bản trình duyệt và nút hiện tại async/await
. Bạn cũng có thể hỗ trợ các môi trường cũ hơn bằng cách chuyển đổi mã của mình sang ES5 với sự trợ giúp của trình tái tạo (hoặc các công cụ sử dụng trình tái tạo, chẳng hạn như Babel ).
Cho phép các hàm chấp nhận các lệnh gọi lại
Một lệnh gọi lại là khi hàm 1 được chuyển cho hàm 2. Hàm 2 có thể gọi hàm 1 bất cứ khi nào nó sẵn sàng. Trong bối cảnh của một quá trình không đồng bộ, lệnh gọi lại sẽ được gọi bất cứ khi nào quá trình không đồng bộ được thực hiện. Thông thường, kết quả được chuyển cho lệnh gọi lại.
Trong ví dụ của câu hỏi, bạn có thể foo
chấp nhận một cuộc gọi lại và sử dụng nó như một success
cuộc gọi lại. Vì vậy, điều này
var result = foo();
// Code that depends on 'result'
trở thành
foo(function(result) {
// Code that depends on 'result'
});
Ở đây chúng tôi đã định nghĩa hàm "inline" nhưng bạn có thể chuyển bất kỳ tham chiếu hàm nào:
function myCallback(result) {
// Code that depends on 'result'
}
foo(myCallback);
foo
chính nó được định nghĩa như sau:
function foo(callback) {
$.ajax({
// ...
success: callback
});
}
callback
sẽ tham chiếu đến hàm mà chúng ta chuyển đến foo
khi chúng ta gọi nó và chúng ta chuyển nó vào success
. Tức là sau khi yêu cầu Ajax thành công, $.ajax
sẽ gọi callback
và chuyển phản hồi đến lệnh gọi lại (có thể được gọi là với result
, vì đây là cách chúng tôi định nghĩa lệnh gọi lại).
Bạn cũng có thể xử lý phản hồi trước khi chuyển nó đến lệnh gọi lại:
function foo(callback) {
$.ajax({
// ...
success: function(response) {
// For example, filter the response
callback(filtered_response);
}
});
}
Viết mã bằng lệnh gọi lại dễ dàng hơn tưởng tượng. Rốt cuộc, JavaScript trong trình duyệt chủ yếu hướng sự kiện (sự kiện DOM). Nhận được phản hồi Ajax không gì khác ngoài một sự kiện.
Khó khăn có thể phát sinh khi bạn phải làm việc với mã của bên thứ ba, nhưng hầu hết các vấn đề có thể được giải quyết bằng cách chỉ cần suy nghĩ thông qua luồng ứng dụng.
ES2015 +: Hứa hẹn với sau đó ()
Các Promise API là một tính năng mới của ECMAScript 6 (ES2015), nhưng nó có tốt hỗ trợ trình duyệt rồi. Cũng có nhiều thư viện triển khai Promises API tiêu chuẩn và cung cấp các phương pháp bổ sung để dễ sử dụng và cấu thành các hàm không đồng bộ (ví dụ: bluebird ).
Những lời hứa là vật chứa đựng những giá trị trong tương lai . Khi lời hứa nhận được giá trị (nó được giải quyết ) hoặc khi nó bị hủy bỏ ( bị từ chối ), nó sẽ thông báo cho tất cả những "người nghe" của nó muốn truy cập giá trị này.
Ưu điểm so với lệnh gọi lại đơn giản là chúng cho phép bạn tách mã của mình và chúng dễ soạn hơn.
Đây là một ví dụ về việc sử dụng một lời hứa:
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).
});
Được áp dụng cho lệnh gọi Ajax của chúng tôi, chúng tôi có thể sử dụng các lời hứa như sau:
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
});
Mô tả tất cả những lợi thế mà lời hứa cung cấp nằm ngoài phạm vi của câu trả lời này, nhưng nếu bạn viết mã mới, bạn nên nghiêm túc xem xét chúng. Chúng cung cấp sự trừu tượng và tách biệt tuyệt vời cho mã của bạn.
Thông tin thêm về lời hứa: HTML5 stone - JavaScript Promises
Lưu ý bên: các đối tượng bị trì hoãn của jQuery
Đối tượng bị trì hoãn là triển khai tùy chỉnh của jQuery đối với các hứa hẹn (trước khi API Hứa hẹn được chuẩn hóa). Chúng hoạt động gần giống như những lời hứa nhưng hiển thị một API hơi khác.
Mọi phương thức Ajax của jQuery đã trả về một "đối tượng được hoãn lại" (thực sự là một lời hứa của một đối tượng được hoãn lại) mà bạn chỉ có thể trả về từ hàm của mình:
function ajax() {
return $.ajax(...);
}
ajax().done(function(result) {
// Code depending on result
}).fail(function() {
// An error occurred
});
Ghi chú bên: Lời hứa gotchas
Hãy nhớ rằng các lời hứa và các đối tượng hoãn lại chỉ là vật chứa cho một giá trị trong tương lai, chúng không phải là giá trị đó. Ví dụ: giả sử bạn có những thứ sau:
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
}
Mã này hiểu sai các vấn đề không đồng bộ ở trên. Cụ thể, $.ajax()
mã không đóng băng trong khi kiểm tra trang '/ password' trên máy chủ của bạn - nó gửi một yêu cầu đến máy chủ và trong khi chờ đợi, nó ngay lập tức trả về một đối tượng jQuery Ajax Deferred, không phải phản hồi từ máy chủ. Điều đó có nghĩa là if
câu lệnh sẽ luôn lấy đối tượng Deferred này, xử lý nó true
và tiến hành như thể người dùng đã đăng nhập. Không tốt.
Nhưng cách khắc phục rất dễ dàng:
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
});
Không được khuyến nghị: Lệnh gọi "Ajax" đồng bộ
Như tôi đã đề cập, một số (!) Hoạt động không đồng bộ có các đối tác đồng bộ. Tôi không ủng hộ việc sử dụng chúng, nhưng vì lợi ích hoàn chỉnh, đây là cách bạn thực hiện một lệnh gọi đồng bộ:
Không có jQuery
Nếu bạn trực tiếp sử dụng một XMLHttpRequest
đối tượng, hãy chuyển false
làm đối số thứ ba cho .open
.
jQuery
Nếu bạn sử dụng jQuery , bạn có thể đặt async
tùy chọn thành false
. Lưu ý rằng tùy chọn này không được dùng nữa kể từ jQuery 1.8. Sau đó, bạn vẫn có thể sử dụng lệnh success
gọi lại hoặc truy cập thuộc responseText
tính của đối tượng jqXHR :
function foo() {
var jqXHR = $.ajax({
//...
async: false
});
return jqXHR.responseText;
}
Nếu bạn sử dụng bất kỳ phương thức jQuery Ajax nào khác, chẳng hạn như $.get
, $.getJSON
v.v., bạn phải thay đổi nó thành $.ajax
(vì bạn chỉ có thể chuyển các tham số cấu hình đến $.ajax
).
Đứng lên! Không thể thực hiện yêu cầu JSONP đồng bộ . Về bản chất, JSONP luôn không đồng bộ (thêm một lý do nữa để không xem xét tùy chọn này).
Nếu bạn không sử dụng jQuery trong mã của mình, câu trả lời này là dành cho bạn
Mã của bạn phải là một cái gì đó dọc theo dòng này:
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 đã làm rất tốt khi viết câu trả lời cho những người sử dụng jQuery cho AJAX, tôi đã quyết định cung cấp một giải pháp thay thế cho những người không sử dụng.
Những gì bạn đang đối mặt
Đây là bản tóm tắt ngắn gọn của "Giải thích vấn đề" từ câu trả lời kia, nếu bạn không chắc chắn sau khi đọc cái này, hãy đọc cái đó.
Các Một trong AJAX là viết tắt của Asynchronous . Điều đó có nghĩa là việc gửi yêu cầu (hay đúng hơn là nhận phản hồi) được đưa ra khỏi quy trình thực thi bình thường. Trong ví dụ của bạn, .send
trả về ngay lập tức và câu lệnh tiếp theo return result;
, được thực thi trước khi hàm bạn đã chuyển dưới dạng success
gọi lại thậm chí được gọi.
Điều này có nghĩa là khi bạn quay lại, trình lắng nghe bạn đã xác định vẫn chưa thực thi, có nghĩa là giá trị bạn đang trả về chưa được xác định.
Đây là một phép loại suy đơn giản
function getFive(){
var a;
setTimeout(function(){
a=5;
},10);
return a;
}
Giá trị được a
trả về là undefined
do a=5
phần chưa được thực thi. AJAX hoạt động như vậy, bạn đang trả lại giá trị trước khi máy chủ có cơ hội cho trình duyệt của bạn biết giá trị đó là gì.
Một giải pháp khả thi cho vấn đề này là kích hoạt lại mã , cho chương trình của bạn biết phải làm gì khi tính toán hoàn tất.
function onComplete(a){ // When the code completes, do this
alert(a);
}
function getFive(whenDone){
var a;
setTimeout(function(){
a=5;
whenDone(a);
},10);
}
Đây được gọi là CPS . Về cơ bản, chúng tôi đang chuyển getFive
một hành động để thực hiện khi nó hoàn thành, chúng tôi đang nói với mã của mình cách phản ứng khi một sự kiện hoàn thành (như lệnh gọi AJAX của chúng tôi hoặc trong trường hợp này là thời gian chờ).
Cách sử dụng sẽ là:
getFive(onComplete);
Mà sẽ cảnh báo "5" trên màn hình. (Chơi đùa) .
Phương pháp khả thi
Về cơ bản có hai cách để giải quyết vấn đề này:
- Thực hiện cuộc gọi AJAX đồng bộ (hãy gọi nó là SJAX).
- Cấu trúc lại mã của bạn để hoạt động bình thường với các lệnh gọi lại.
1. AJAX đồng bộ - Đừng làm điều đó !!
Còn đối với AJAX đồng bộ thì không nên! Câu trả lời của Felix đưa ra một số lý lẽ thuyết phục về lý do tại sao đó là một ý tưởng tồi. Tóm lại, nó sẽ đóng băng trình duyệt của người dùng cho đến khi máy chủ trả về phản hồi và tạo ra trải nghiệm người dùng rất tệ. Đây là một bản tóm tắt ngắn khác được lấy từ MDN về lý do:
XMLHttpRequest hỗ trợ cả truyền thông đồng bộ và không đồng bộ. Tuy nhiên, nói chung, các yêu cầu không đồng bộ nên được ưu tiên hơn các yêu cầu đồng bộ vì lý do hiệu suất.
Tóm lại, các yêu cầu đồng bộ chặn việc thực thi mã ... ... điều này có thể gây ra các vấn đề nghiêm trọng ...
Nếu bạn phải làm điều đó, bạn có thể vượt qua một cờ: Đây là cách thực hiện:
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. Tái cấu trúc mã
Cho phép hàm của bạn chấp nhận một cuộc gọi lại. Trong mã ví dụ foo
có thể được thực hiện để chấp nhận một cuộc gọi lại. Chúng tôi sẽ cho mã của chúng tôi biết cách phản ứng khi foo
hoàn tất.
Vì thế:
var result = foo();
// code that depends on `result` goes here
Trở thành:
foo(function(result) {
// code that depends on `result`
});
Ở đây chúng tôi đã truyền một hàm ẩn danh, nhưng chúng tôi có thể dễ dàng chuyển một tham chiếu đến một hàm hiện có, làm cho nó trông giống như:
function myHandler(result) {
// code that depends on `result`
}
foo(myHandler);
Để biết thêm chi tiết về cách loại thiết kế gọi lại này được thực hiện, hãy xem câu trả lời của Felix.
Bây giờ, hãy xác định chính foo để hành động cho phù hợp
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();
}
Bây giờ chúng tôi đã làm cho hàm foo của chúng tôi chấp nhận một hành động để chạy khi AJAX hoàn tất thành công, chúng tôi có thể mở rộng điều này hơn nữa bằng cách kiểm tra nếu trạng thái phản hồi không phải là 200 và hoạt động tương ứng (tạo trình xử lý thất bại và tương tự). Giải quyết hiệu quả vấn đề của chúng tôi.
Nếu bạn vẫn gặp khó khăn trong việc hiểu điều này, hãy đọc hướng dẫn bắt đầu AJAX tại MDN.
XMLHttpRequest 2 (trước hết hãy đọc câu trả lời từ Benjamin Gruenbaum & Felix Kling )
Nếu bạn không sử dụng jQuery và muốn có một XMLHttpRequest 2 ngắn gọn đẹp mắt hoạt động trên các trình duyệt hiện đại và cả trên các trình duyệt di động, tôi khuyên bạn nên sử dụng nó theo cách này:
function ajax(a, b, c){ // URL, callback, just a placeholder
c = new XMLHttpRequest;
c.open('GET', a);
c.onload = b;
c.send()
}
Bạn có thể thấy:
- Nó ngắn hơn tất cả các chức năng khác được liệt kê.
- Gọi lại được đặt trực tiếp (vì vậy không có thêm các lần đóng không cần thiết).
- Nó sử dụng onload mới (vì vậy bạn không phải kiểm tra trạng thái sẵn sàng &&)
- Có một số tình huống khác mà tôi không nhớ làm cho XMLHttpRequest 1 khó chịu.
Có hai cách để nhận phản hồi của lệnh gọi Ajax này (ba cách sử dụng tên var XMLHttpRequest):
Điều đơn giản nhất:
this.response
Hoặc nếu vì lý do nào đó bạn bind()
gọi lại một lớp:
e.target.response
Thí dụ:
function callback(e){
console.log(this.response);
}
ajax('URL', callback);
Hoặc (cái ở trên tốt hơn là các hàm ẩn danh luôn là một vấn đề):
ajax('URL', function(e){console.log(this.response)});
Không có gì dễ dàng hơn.
Bây giờ một số người có thể sẽ nói rằng tốt hơn nên sử dụng onreadystatechange hoặc thậm chí là tên biến XMLHttpRequest. Sai rồi.
Kiểm tra các tính năng nâng cao của XMLHttpRequest
Nó hỗ trợ tất cả * trình duyệt hiện đại. Và tôi có thể xác nhận rằng tôi đang sử dụng phương pháp này vì XMLHttpRequest 2 tồn tại. Tôi chưa bao giờ gặp bất kỳ loại sự cố nào trên tất cả các trình duyệt mà tôi sử dụng.
onreadystatechange chỉ hữu ích nếu bạn muốn lấy tiêu đề ở trạng thái 2.
Sử dụng XMLHttpRequest
tên biến là một lỗi lớn khác vì bạn cần thực hiện lệnh gọi lại bên trong các lần đóng onload / oreadystatechange nếu không bạn đã đánh mất nó.
Bây giờ nếu bạn muốn một cái gì đó phức tạp hơn bằng cách sử dụng post và FormData, bạn có thể dễ dàng mở rộng chức năng này:
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)
}
Một lần nữa ... nó là một hàm rất ngắn, nhưng nó có được và đăng.
Ví dụ về cách sử dụng:
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
Hoặc chuyển một phần tử biểu mẫu đầy đủ ( document.getElementsByTagName('form')[0]
):
var fd = new FormData(form);
x(url, callback, 'post', fd);
Hoặc đặt một số giá trị tùy chỉnh:
var fd = new FormData();
fd.append('key', 'val')
x(url, callback, 'post', fd);
Như bạn có thể thấy, tôi đã không triển khai đồng bộ hóa ... đó là một điều tồi tệ.
Đã nói rằng ... tại sao không làm điều đó một cách dễ dàng?
Như đã đề cập trong nhận xét, việc sử dụng error && sync hoàn toàn phá vỡ quan điểm của câu trả lời. Cách ngắn gọn hay để sử dụng Ajax theo cách thích hợp là gì?
Xử lý lỗi
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);
Trong tập lệnh trên, bạn có một trình xử lý lỗi được định nghĩa tĩnh để nó không ảnh hưởng đến chức năng. Trình xử lý lỗi cũng có thể được sử dụng cho các chức năng khác.
Nhưng để thực sự thoát ra lỗi, cách duy nhất là viết sai URL trong trường hợp đó mọi trình duyệt đều gặp lỗi.
Trình xử lý lỗi có thể hữu ích nếu bạn đặt tiêu đề tùy chỉnh, đặt responseType thành bộ đệm mảng blob hoặc bất cứ thứ gì ...
Ngay cả khi bạn chuyển 'POSTAPAPAP' làm phương thức, nó sẽ không gây ra lỗi.
Ngay cả khi bạn chuyển 'fdggdgilfdghfldj' làm dữ liệu biểu mẫu, nó sẽ không xuất hiện lỗi.
Trong trường hợp đầu tiên, lỗi là bên trong displayAjax()
dưới this.statusText
như Method not Allowed
.
Trong trường hợp thứ hai, nó chỉ đơn giản là hoạt động. Bạn phải kiểm tra ở phía máy chủ xem bạn đã chuyển đúng dữ liệu bài đăng chưa.
tên miền chéo không được phép ném lỗi tự động.
Trong phản hồi lỗi, không có mã lỗi.
Chỉ có this.type
cái được đặt thành lỗi.
Tại sao phải thêm trình xử lý lỗi nếu bạn hoàn toàn không có quyền kiểm soát lỗi? Hầu hết các lỗi được trả về bên trong hàm này trong hàm gọi lại displayAjax()
.
Vì vậy: Không cần kiểm tra lỗi nếu bạn có thể sao chép và dán URL đúng cách. ;)
PS: Là bài kiểm tra đầu tiên tôi viết x ('x', displayAjax) ..., và nó hoàn toàn nhận được phản hồi ... ??? Vì vậy, tôi đã kiểm tra thư mục chứa HTML và có một tệp tên là 'x.xml'. Vì vậy, ngay cả khi bạn quên phần mở rộng của tệp XMLHttpRequest 2 SẼ TÌM HIỂU . Tôi LOL'd
Đọc đồng bộ tệp
Đừng làm vậy.
Nếu bạn muốn chặn trình duyệt trong một thời gian, hãy tải .txt
đồng bộ tệp lớn tốt đẹp .
function omg(a, c){ // URL
c = new XMLHttpRequest;
c.open('GET', a, true);
c.send();
return c; // Or c.response
}
Bây giờ bạn có thể làm
var res = omg('thisIsGonnaBlockThePage.txt');
Không có cách nào khác để làm điều này theo cách không đồng bộ. (Vâng, với vòng lặp setTimeout ... nhưng nghiêm túc chứ?)
Một điểm khác là ... nếu bạn làm việc với các API hoặc chỉ các tệp trong danh sách của riêng bạn hoặc bất cứ thứ gì bạn luôn sử dụng các chức năng khác nhau cho mỗi yêu cầu ...
Chỉ khi bạn có một trang mà bạn luôn tải cùng một XML / JSON hoặc bất cứ thứ gì bạn chỉ cần một chức năng. Trong trường hợp đó, hãy sửa đổi một chút hàm Ajax và thay thế b bằng hàm đặc biệt của bạn.
Các chức năng trên sử dụng cơ bản.
Nếu bạn muốn MỞ RỘNG chức năng ...
Có, bạn có thể.
Tôi đang sử dụng rất nhiều API và một trong những hàm đầu tiên tôi tích hợp vào mỗi trang HTML là hàm Ajax đầu tiên trong câu trả lời này, chỉ với GET ...
Nhưng bạn có thể làm được nhiều thứ với XMLHttpRequest 2:
Tôi đã tạo trình quản lý tải xuống (sử dụng phạm vi ở cả hai phía với sơ yếu lý lịch, trình đọc tệp, hệ thống tệp), các bộ chuyển đổi kích thước hình ảnh khác nhau bằng canvas, điền cơ sở dữ liệu SQL web với base64images và hơn thế nữa ... Nhưng trong những trường hợp này, bạn nên tạo một hàm chỉ dành cho điều đó mục đích ... đôi khi bạn cần một đốm màu, bộ đệm mảng, bạn có thể đặt tiêu đề, ghi đè mimetype và hơn thế nữa ...
Nhưng câu hỏi ở đây là làm thế nào để trả về một phản hồi Ajax ... (Tôi đã thêm một cách dễ dàng.)
Nếu bạn đang sử dụng lời hứa, câu trả lời này là dành cho bạn.
Điều này có nghĩa là AngularJS, jQuery (có hoãn lại), thay thế của XHR gốc (tìm nạp), EmberJS, lưu của BackboneJS hoặc bất kỳ thư viện nút nào trả về các hứa hẹn.
Mã của bạn phải là một cái gì đó dọc theo dòng này:
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 đã làm rất tốt khi viết câu trả lời cho những người sử dụng jQuery với các lệnh gọi lại cho AJAX. Tôi có câu trả lời cho XHR bản địa. Câu trả lời này dành cho việc sử dụng chung các lời hứa trên giao diện người dùng hoặc phụ trợ.
Vấn đề cốt lõi
Mô hình đồng thời JavaScript trong trình duyệt và trên máy chủ với NodeJS / io.js là không đồng bộ và phản ứng .
Bất cứ khi nào bạn gọi một phương thức trả về một lời hứa, các then
trình xử lý luôn được thực thi không đồng bộ - nghĩa là sau đoạn mã bên dưới chúng mà không có trong .then
trình xử lý.
Điều này có nghĩa khi bạn trở về data
các then
handler bạn đã xác định không thực hiện được nêu ra. Điều này có nghĩa là giá trị bạn đang trả về đã không được đặt thành giá trị chính xác trong thời gian.
Đây là một phép tương tự đơn giản cho vấn đề:
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
Giá trị của data
là undefined
vì data = 5
phần chưa được thực thi. Nó có thể sẽ thực thi trong một giây nhưng tại thời điểm đó nó không liên quan đến giá trị trả về.
Vì hoạt động chưa xảy ra (AJAX, lệnh gọi máy chủ, IO, bộ đếm thời gian) bạn đang trả về giá trị trước khi yêu cầu có cơ hội cho mã của bạn biết giá trị đó là gì.
Một giải pháp khả thi cho vấn đề này là kích hoạt lại mã , cho chương trình của bạn biết phải làm gì khi tính toán hoàn tất. Những lời hứa tích cực kích hoạt điều này bằng cách tự nhiên (nhạy cảm với thời gian).
Tóm tắt nhanh về những lời hứa
Lời hứa là một giá trị theo thời gian . Lời hứa có trạng thái, chúng bắt đầu như đang chờ xử lý không có giá trị và có thể giải quyết thành:
- hoàn thành nghĩa là tính toán đã hoàn tất thành công.
- bị từ chối nghĩa là tính toán không thành công.
Một lời hứa chỉ có thể thay đổi trạng thái một lần sau đó nó sẽ luôn ở trạng thái cũ mãi mãi. Bạn có thể đính kèm các then
trình xử lý vào các lời hứa để trích xuất giá trị của chúng và xử lý lỗi. then
trình xử lý cho phép xâu chuỗi các cuộc gọi. Lời hứa được tạo ra bằng cách sử dụng các API trả về chúng . Ví dụ: thay thế AJAX hiện đại hơn fetch
hoặc $.get
lời hứa trả lại của jQuery .
Khi chúng ta yêu cầu .then
một lời hứa và trả lại một cái gì đó từ nó - chúng ta sẽ nhận được một lời hứa cho giá trị được xử lý . Nếu chúng ta trả lại một lời hứa khác, chúng ta sẽ nhận được những điều tuyệt vời, nhưng hãy giữ lấy con ngựa của chúng ta.
Với những lời hứa
Hãy xem làm thế nào chúng ta có thể giải quyết vấn đề trên với những lời hứa. Đầu tiên, chúng ta hãy chứng minh sự hiểu biết của chúng ta về các trạng thái hứa từ trên bằng cách sử dụng hàm tạo Promise để tạo một hàm trì hoãn:
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);
});
}
Bây giờ, sau khi chúng tôi chuyển đổi setTimeout để sử dụng các lời hứa, chúng tôi có thể sử dụng then
để làm cho nó có giá trị:
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;
});
Về cơ bản, thay vì trả lại một giá trị mà chúng tôi không thể làm được vì mô hình đồng thời - chúng tôi trả lại một wrapper cho một giá trị mà chúng ta có thể unwrap với then
. Nó giống như một chiếc hộp bạn có thể mở bằng then
.
Áp dụng cái này
Điều này giống với lệnh gọi API ban đầu của bạn, bạn có thể:
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`
})
Vì vậy, điều này cũng hoạt động. Chúng tôi đã biết rằng chúng tôi không thể trả về các giá trị từ các lệnh gọi đã không đồng bộ nhưng chúng tôi có thể sử dụng các lời hứa và chuỗi chúng để thực hiện xử lý. Bây giờ chúng ta biết cách trả lại phản hồi từ một cuộc gọi không đồng bộ.
ES2015 (ES6)
ES6 giới thiệu các trình tạo là các chức năng có thể quay trở lại ở giữa và sau đó tiếp tục lại điểm mà chúng đã ở đó. Điều này thường hữu ích cho các chuỗi, ví dụ:
function* foo(){ // notice the star, this is ES6 so new browsers/node/io only
yield 1;
yield 2;
while(true) yield 3;
}
Là một hàm trả về một trình lặp trên chuỗi 1,2,3,3,3,3,....
có thể được lặp lại. Trong khi điều này tự nó là thú vị và mở ra nhiều khả năng có một trường hợp thú vị đặc biệt.
Nếu trình tự mà chúng tôi đang tạo ra là một chuỗi các hành động chứ không phải là số - chúng tôi có thể tạm dừng chức năng bất cứ khi nào một hành động được tạo ra và đợi nó trước khi chúng tôi tiếp tục chức năng. Vì vậy, thay vì một dãy số, chúng ta cần một dãy giá trị tương lai - đó là: lời hứa.
Thủ thuật hơi phức tạp nhưng rất mạnh mẽ này cho phép chúng ta viết mã không đồng bộ một cách đồng bộ. Có một số "người chạy" làm điều này cho bạn, viết một là một vài dòng mã ngắn nhưng nằm ngoài phạm vi của câu trả lời này. Tôi sẽ sử dụng Bluebird's Promise.coroutine
ở đây, nhưng có những trình bao bọc khác như co
hoặc 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
});
Phương thức này trả về chính một lời hứa mà chúng ta có thể sử dụng từ các coroutines khác. Ví dụ:
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)
Trong ES7, điều này được tiêu chuẩn hóa hơn nữa, có một số đề xuất ngay bây giờ nhưng trong tất cả chúng, bạn có thể await
hứa. Đây chỉ là "đường" (cú pháp đẹp hơn) cho đề xuất ES6 ở trên bằng cách thêm async
và await
từ khóa. Làm ví dụ trên:
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
}
Nó vẫn trả về một lời hứa như cũ :)
Bạn đang sử dụng Ajax không đúng cách. Ý tưởng không phải là để nó trả về bất cứ thứ gì, mà thay vào đó, chuyển dữ liệu cho một thứ gọi là hàm gọi lại, hàm này xử lý dữ liệu.
Đó là:
function handleData( responseData ) {
// Do what you want with the data
console.log(responseData);
}
$.ajax({
url: "hi.php",
...
success: function ( data, status, XHR ) {
handleData(data);
}
});
Trả lại bất cứ thứ gì trong trình xử lý gửi sẽ không làm được gì cả. Thay vào đó, bạn phải xử lý dữ liệu hoặc làm những gì bạn muốn với nó ngay bên trong hàm thành công.
Giải pháp đơn giản nhất là tạo một hàm JavaScript và gọi nó cho lệnh success
gọi lại Ajax .
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);
});
Tôi sẽ trả lời bằng một bộ truyện tranh vẽ tay trông rất kinh khủng. Hình ảnh thứ hai là lý do tại sao result
là undefined
trong ví dụ mã của bạn.
Angular1
Đối với những người đang sử dụng AngularJS , có thể xử lý tình huống này bằng cách sử dụng Promises
.
Ở đây nó nói,
Prom Promise có thể được sử dụng để hủy hợp nhất các chức năng không đồng bộ và cho phép một chuỗi nhiều chức năng lại với nhau.
Bạn cũng có thể tìm thấy một lời giải thích hay ở đây .
Ví dụ được tìm thấy trong các tài liệu được đề cập bên dưới.
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 trở lên
Trong Angular2
với hãy xem ví dụ sau, nhưng nó được khuyến khích sử dụng Observables
với Angular2
.
search(term: string) {
return this.http
.get(`https://api.spotify.com/v1/search?q=${term}&type=artist`)
.map((response) => response.json())
.toPromise();
}
Bạn có thể sử dụng nó theo cách này,
search() {
this.searchService.search(this.searchField.value)
.then((result) => {
this.result = result.artists.items;
})
.catch((error) => console.error(error));
}
Xem bài gốc tại đây. Nhưng Typecript không hỗ trợ các Promise es6 gốc , nếu bạn muốn sử dụng nó, bạn có thể cần plugin cho điều đó.
Ngoài ra, đây là thông số kỹ thuật hứa hẹn được xác định ở đây.
Hầu hết các câu trả lời ở đây đều đưa ra các gợi ý hữu ích khi bạn có một thao tác không đồng bộ, nhưng đôi khi, điều này xuất hiện khi bạn cần thực hiện một thao tác không đồng bộ cho mỗi mục nhập trong một mảng hoặc cấu trúc giống danh sách khác. Sự cám dỗ là làm điều này:
// WRONG
var results = [];
theArray.forEach(function(entry) {
doSomethingAsync(entry, function(result) {
results.push(result);
});
});
console.log(results); // E.g., using them, returning them, etc.
Thí dụ:
// 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;
}
Lý do không hoạt động là các lệnh gọi lại từ doSomethingAsync
vẫn chưa chạy vào thời điểm bạn đang cố gắng sử dụng kết quả.
Vì vậy, nếu bạn có một mảng (hoặc một danh sách nào đó) và muốn thực hiện các thao tác không đồng bộ cho mỗi mục nhập, bạn có hai tùy chọn: Thực hiện các thao tác song song (chồng chéo) hoặc theo chuỗi (nối tiếp nhau theo thứ tự).
Song song, tương đông
Bạn có thể bắt đầu tất cả chúng và theo dõi số lần gọi lại mà bạn đang mong đợi, sau đó sử dụng kết quả khi bạn nhận được nhiều lệnh gọi lại đó:
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
}
});
});
Thí dụ:
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;
}
(Chúng tôi có thể loại bỏ expecting
và chỉ sử dụng results.length === theArray.length
, nhưng điều đó khiến chúng tôi mở ra khả năng theArray
bị thay đổi trong khi các cuộc gọi vẫn còn tồn tại ...)
Lưu ý cách chúng tôi sử dụng index
từ forEach
để lưu kết quả ở results
cùng vị trí với mục nhập mà nó liên quan, ngay cả khi kết quả đến không theo thứ tự (vì các lệnh gọi không đồng bộ không nhất thiết phải hoàn thành theo thứ tự mà chúng được bắt đầu).
Nhưng nếu bạn cần trả lại những kết quả đó từ một hàm thì sao? Như các câu trả lời khác đã chỉ ra, bạn không thể; bạn phải có hàm của bạn chấp nhận và gọi một cuộc gọi lại (hoặc trả về một Lời hứa ). Đây là phiên bản gọi lại:
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);
});
Thí dụ:
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;
}
Hoặc đây là một phiên bản trả về Promise
thay thế:
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);
});
Tất nhiên, nếu doSomethingAsync
chúng tôi thông qua lỗi, chúng tôi sẽ sử dụng reject
để từ chối lời hứa khi chúng tôi có lỗi.)
Thí dụ:
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;
}
(Hoặc cách khác, bạn có thể tạo một trình bao bọc để doSomethingAsync
trả về một lời hứa và sau đó thực hiện như bên dưới ...)
Nếu doSomethingAsync
đưa cho bạn một Lời hứa , bạn có thể sử dụng Promise.all
:
function doSomethingWith(theArray) {
return Promise.all(theArray.map(function(entry) {
return doSomethingAsync(entry);
}));
}
doSomethingWith(theArray).then(function(results) {
console.log("Results:", results);
});
Nếu bạn biết điều đó doSomethingAsync
sẽ bỏ qua đối số thứ hai và thứ ba, bạn có thể chuyển trực tiếp đối số đó tới map
( map
gọi lệnh gọi lại của nó với ba đối số, nhưng hầu hết mọi người chỉ sử dụng phần lớn thời gian đầu tiên):
function doSomethingWith(theArray) {
return Promise.all(theArray.map(doSomethingAsync));
}
doSomethingWith(theArray).then(function(results) {
console.log("Results:", results);
});
Thí dụ:
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;
}
Lưu ý rằng Promise.all
giải quyết lời hứa của nó bằng một loạt các kết quả của tất cả các lời hứa mà bạn đưa ra khi tất cả chúng đã được giải quyết, hoặc từ chối lời hứa khi lời hứa đầu tiên mà bạn đưa ra từ chối.
Loạt
Giả sử bạn không muốn các hoạt động diễn ra song song? Nếu bạn muốn chạy chúng lần lượt, bạn cần đợi từng thao tác hoàn thành trước khi bắt đầu thao tác tiếp theo. Đây là ví dụ về một hàm thực hiện điều đó và gọi một lệnh gọi lại với kết quả:
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);
});
(Vì chúng tôi đang thực hiện công việc theo chuỗi, chúng tôi chỉ có thể sử dụng results.push(result)
vì chúng tôi biết rằng chúng tôi sẽ không nhận được kết quả không theo thứ tự. Ở trên, chúng tôi có thể đã sử dụng results[index] = result;
, nhưng trong một số ví dụ sau, chúng tôi không có chỉ mục để sử dụng.)
Thí dụ:
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;
}
(Hoặc, một lần nữa, xây dựng một trình bao bọc để doSomethingAsync
cung cấp cho bạn một lời hứa và thực hiện những điều bên dưới ...)
Nếu doSomethingAsync
cung cấp cho bạn một Lời hứa, nếu bạn có thể sử dụng cú pháp ES2017 + (có thể với một trình chuyển tiếp như Babel ), bạn có thể sử dụng một async
hàm với for-of
và 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);
});
Thí dụ:
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;
}
Nếu bạn không thể sử dụng cú pháp ES2017 + (chưa), bạn có thể sử dụng một biến thể trên mẫu "Promise Reduce" (điều này phức tạp hơn so với Promise Reduce thông thường vì chúng tôi không chuyển kết quả từ cái này sang cái tiếp theo mà thay vào đó thu thập kết quả của họ trong một mảng):
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);
});
Thí dụ:
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;
}
... ít cồng kềnh hơn với các hàm mũi tên của ES2015 + :
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);
});
Thí dụ:
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;
}
Hãy xem ví dụ này:
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);
});
});
Như bạn có thể thấy getJoke
là trả về một lời hứa đã giải quyết (nó được giải quyết khi quay trở lại res.data.value
). Vì vậy, bạn đợi cho đến khi yêu cầu $ http.get được hoàn thành và sau đó console.log (res.joke) được thực thi (như một luồng không đồng bộ thông thường).
Đây là plnkr:
http://embed.plnkr.co/XlNR7HpCaIhJxskMJfSg/
Cách ES6 (không đồng bộ - đang chờ)
(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);
});
})();
Đây là một trong những nơi mà hai cách liên kết dữ liệu hoặc khái niệm lưu trữ được sử dụng trong nhiều khung JavaScript mới sẽ hoạt động tốt cho bạn ...
Vì vậy, nếu bạn đang sử dụng Angular, React hoặc bất kỳ khung công tác nào khác có hai cách liên kết dữ liệu hoặc lưu trữ khái niệm , vấn đề này chỉ được khắc phục cho bạn, vì vậy, nói một cách dễ hiểu, kết quả của bạn là undefined
ở giai đoạn đầu tiên, vì vậy bạn có result = undefined
trước khi nhận được dữ liệu, sau đó ngay khi bạn nhận được kết quả, nó sẽ được cập nhật và được gán cho giá trị mới mà phản hồi của lệnh gọi Ajax của bạn ...
Nhưng làm thế nào bạn có thể làm điều đó trong javascript thuần túy hoặc jQuery, ví dụ như bạn đã hỏi trong câu hỏi này?
Bạn có thể sử dụng một lệnh gọi lại , lời hứa và có thể quan sát được gần đây để xử lý nó cho bạn, ví dụ: trong các lời hứa, chúng tôi có một số chức năng như success()
hoặc then()
sẽ được thực thi khi dữ liệu của bạn sẵn sàng cho bạn, tương tự với chức năng gọi lại hoặc đăng ký trên có thể quan sát .
Ví dụ, trong trường hợp bạn đang sử dụng jQuery , bạn có thể làm như sau:
$(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
});
Để biết thêm thông tin, hãy nghiên cứu về các hứa hẹn và khả năng quan sát là những cách mới hơn để thực hiện nội dung không đồng bộ này.
Đó là một vấn đề rất phổ biến mà chúng tôi gặp phải khi đấu tranh với những 'bí ẩn' của JavaScript. Hãy để tôi thử làm sáng tỏ bí ẩn này ngày hôm nay.
Hãy bắt đầu với một hàm JavaScript đơn giản:
function foo(){
// do something
return 'wohoo';
}
let bar = foo(); // bar is 'wohoo' here
Đó là một lệnh gọi hàm đồng bộ đơn giản (trong đó mỗi dòng mã được 'hoàn thành xong công việc của nó' trước dòng tiếp theo trong chuỗi) và kết quả giống như mong đợi.
Bây giờ chúng ta hãy thêm một chút xoắn, bằng cách giới thiệu một chút độ trễ trong hàm của chúng ta, để tất cả các dòng mã không được 'kết thúc' theo trình tự. Do đó, nó sẽ mô phỏng hành vi không đồng bộ của hàm:
function foo(){
setTimeout( ()=>{
return 'wohoo';
}, 1000 )
}
let bar = foo() // bar is undefined here
Vậy là xong, sự chậm trễ đó đã phá vỡ chức năng mà chúng tôi mong đợi! Nhưng chính xác thì điều gì đã xảy ra? Chà, nó thực sự khá hợp lý nếu bạn nhìn vào mã. hàm foo()
, khi thực thi, không trả về gì (do đó giá trị trả về là undefined
), nhưng nó khởi động bộ đếm thời gian, thực thi một hàm sau 1 giây để trả về 'wohoo'. Nhưng như bạn có thể thấy, giá trị được gán cho bar là thứ được trả về ngay lập tức từ foo (), không có nghĩa là chỉ undefined
.
Vì vậy, làm thế nào để chúng tôi giải quyết vấn đề này?
Hãy yêu cầu chức năng của chúng tôi cho một LỜI HỨA . Promise thực sự là về ý nghĩa của nó: nó có nghĩa là hàm đảm bảo bạn cung cấp bất kỳ đầu ra nào mà nó nhận được trong tương lai. vì vậy chúng ta hãy xem nó hoạt động cho vấn đề nhỏ của chúng tôi ở trên:
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'
});
Vì vậy, tóm tắt là - để giải quyết các hàm không đồng bộ như lệnh gọi dựa trên ajax, v.v., bạn có thể sử dụng một lời hứa cho resolve
giá trị (mà bạn định trả về). Vì vậy, trong ngắn hạn, bạn giải quyết giá trị thay vì trả về , trong các hàm không đồng bộ.
CẬP NHẬT (Hứa hẹn với async / await)
Ngoài việc sử dụng then/catch
để làm việc với các lời hứa, còn tồn tại một cách tiếp cận khác. Ý tưởng là nhận ra một chức năng không đồng bộ và sau đó đợi các lời hứa giải quyết, trước khi chuyển sang dòng mã tiếp theo. Nó vẫn chỉ là ẩn promises
, nhưng với một cách tiếp cận cú pháp khác. Để làm cho mọi thứ rõ ràng hơn, bạn có thể tìm thấy một so sánh dưới đây:
sau đó / bắt phiên bản:
function saveUsers(){
getUsers()
.then(users => {
saveSomewhere(users);
})
.catch(err => {
console.error(err);
})
}
phiên bản async / await:
async function saveUsers(){
try{
let users = await getUsers()
saveSomewhere(users);
}
catch(err){
console.error(err);
}
}
Một cách tiếp cận khác để trả về giá trị từ một hàm không đồng bộ là truyền vào một đối tượng sẽ lưu trữ kết quả từ hàm không đồng bộ.
Đây là một ví dụ tương tự:
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');
});
Tôi đang sử dụng result
đối tượng để lưu trữ giá trị trong quá trình hoạt động không đồng bộ. Điều này cho phép kết quả có sẵn ngay cả sau công việc không đồng bộ.
Tôi sử dụng cách tiếp cận này rất nhiều. Tôi muốn biết cách tiếp cận này hoạt động tốt như thế nào khi liên kết kết quả trở lại thông qua các mô-đun liên tiếp.
Mặc dù những lời hứa và cuộc gọi lại hoạt động tốt trong nhiều tình huống, nhưng thật khó để thể hiện những điều như sau:
if (!name) {
name = async1();
}
async2(name);
Bạn sẽ trải qua async1
; kiểm tra xem có phải name
là không xác định hay không và gọi lại cho phù hợp.
async1(name, callback) {
if (name)
callback(name)
else {
doSomething(callback)
}
}
async1(name, async2)
Mặc dù không sao trong những ví dụ nhỏ nhưng nó sẽ gây khó chịu khi bạn gặp phải nhiều trường hợp tương tự và việc xử lý lỗi liên quan.
Fibers
giúp giải quyết vấn đề.
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
}
Bạn có thể kiểm tra dự án tại đây .
Ví dụ sau đây tôi đã viết cho thấy cách
- Xử lý các cuộc gọi HTTP không đồng bộ;
- Chờ phản hồi từ mỗi lệnh gọi API;
- Sử dụng mẫu Promise ;
- Sử dụng mẫu Promise.all để tham gia nhiều cuộc gọi HTTP;
Ví dụ làm việc này là khép kín. Nó sẽ xác định một đối tượng yêu cầu đơn giản sử dụng XMLHttpRequest
đối tượng cửa sổ để thực hiện các cuộc gọi. Nó sẽ xác định một chức năng đơn giản để đợi một loạt các lời hứa được hoàn thành.
Bối cảnh. Ví dụ đang truy vấn điểm cuối API Web Spotify để tìm kiếm playlist
các đối tượng cho một tập hợp các chuỗi truy vấn nhất định:
[
"search?type=playlist&q=%22doom%20metal%22",
"search?type=playlist&q=Adele"
]
Đối với mỗi mục, một Lời hứa mới sẽ kích hoạt một khối - ExecutionBlock
, phân tích cú pháp kết quả, lập lịch cho một bộ lời hứa mới dựa trên mảng kết quả, đó là danh sách các user
đối tượng Spotify và thực hiện lệnh gọi HTTP mới trong ExecutionProfileBlock
không đồng bộ.
Sau đó, bạn có thể thấy cấu trúc Promise lồng nhau, cho phép bạn tạo ra nhiều cuộc gọi HTTP lồng nhau hoàn toàn không đồng bộ và kết hợp các kết quả từ mỗi tập con các cuộc gọi thông qua Promise.all
.
LƯU Ý
Các search
API Spotify gần đây sẽ yêu cầu mã thông báo truy cập được chỉ định trong tiêu đề yêu cầu:
-H "Authorization: Bearer {your access token}"
Vì vậy, để chạy ví dụ sau, bạn cần đặt mã thông báo truy cập của mình trong tiêu đề yêu cầu:
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" />
Tôi đã thảo luận rộng rãi về giải pháp này ở đây .
Câu trả lời ngắn gọn là, bạn phải triển khai một lệnh gọi lại như sau:
function callback(response) {
// Here you can do what ever you want with the response object.
console.log(response);
}
$.ajax({
url: "...",
success: callback
});
Câu trả lời năm 2017: bây giờ bạn có thể làm chính xác những gì bạn muốn trong mọi trình duyệt và nút hiện tại
Điều này khá đơn giản:
- Trả lại một lời hứa
- Sử dụng 'await' , điều này sẽ cho JavaScript biết chờ đợi lời hứa được phân giải thành một giá trị (như phản hồi HTTP)
- Thêm từ khóa 'async' vào hàm mẹ
Đây là phiên bản mã của bạn đang hoạt động:
(async function(){
var response = await superagent.get('...')
console.log(response)
})()
await được hỗ trợ trong tất cả các trình duyệt hiện tại và nút 8
Js là một luồng đơn.
Trình duyệt có thể được chia thành ba phần:
1) Vòng lặp sự kiện
2) API web
3) Hàng đợi sự kiện
Vòng lặp sự kiện chạy mãi mãi, tức là loại vòng lặp vô hạn. Hàng đợi sự kiện là nơi tất cả chức năng của bạn được đẩy trên một số sự kiện (ví dụ: nhấp chuột), đây là từng người một được thực hiện trong hàng đợi và đưa vào Vòng lặp sự kiện thực thi chức năng này và chuẩn bị cho chính nó cho cái tiếp theo sau khi cái đầu tiên được thực thi Điều này có nghĩa là Việc thực thi một hàm không bắt đầu cho đến khi hàm trước khi nó trong hàng đợi được thực thi trong vòng lặp sự kiện.
Bây giờ chúng ta hãy nghĩ rằng chúng ta đã đẩy hai hàm trong một hàng đợi, một là để lấy dữ liệu từ máy chủ và một hàm khác sử dụng dữ liệu đó. Chúng ta đã đẩy hàm serverRequest () trong hàng đợi trước rồi đến hàm useiseData (). Hàm serverRequest đi trong vòng lặp sự kiện và thực hiện cuộc gọi đến máy chủ vì chúng tôi không biết sẽ mất bao nhiêu thời gian để lấy dữ liệu từ máy chủ, vì vậy quá trình này dự kiến sẽ mất thời gian và do đó chúng tôi bận rộn vòng lặp sự kiện của mình, do đó trang của chúng tôi bị treo, đó là nơi Web API phát huy vai trò của nó, nó lấy chức năng này từ vòng lặp sự kiện và xử lý với máy chủ làm cho vòng lặp sự kiện miễn phí để chúng ta có thể thực thi hàm tiếp theo từ hàng đợi. Hàm tiếp theo trong hàng đợi là useiseData () đi trong vòng lặp nhưng do không có sẵn dữ liệu nên nó hoạt động lãng phí và việc thực thi chức năng tiếp theo tiếp tục cho đến khi kết thúc hàng đợi. (Đây được gọi là gọi Async tức là chúng ta có thể làm điều gì đó khác cho đến khi nhận được dữ liệu)
Giả sử hàm serverRequest () của chúng tôi có câu lệnh trả về trong một đoạn mã, khi chúng tôi lấy lại dữ liệu từ máy chủ Web API sẽ đẩy nó vào hàng đợi ở cuối hàng đợi. Vì nó được đẩy vào cuối hàng đợi, chúng tôi không thể sử dụng dữ liệu của nó vì không còn chức năng nào trong hàng đợi của chúng tôi để sử dụng dữ liệu này. Do đó, không thể trả lại một cái gì đó từ Async Call.
Vì vậy, Giải pháp cho điều này là gọi lại hoặc lời hứa .
A Hình ảnh từ một trong các câu trả lời ở đây, Giải thích chính xác việc sử dụng callback ... Chúng tôi cung cấp chức năng của chúng tôi (chức năng sử dụng dữ liệu trả về từ máy chủ) cho chức năng gọi máy chủ.
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);
}
Trong Mã của tôi, nó được gọi là
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");
}
Bạn có thể sử dụng thư viện tùy chỉnh này (được viết bằng Promise) để thực hiện cuộc gọi từ xa.
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);
};
});
}
Ví dụ sử dụng đơn giản:
$http({
method: 'get',
url: 'google.com'
}).then(function(response) {
console.log(response);
}, function(error) {
console.log(error)
});
Một giải pháp khác là thực thi mã thông qua trình thực thi tuần tự nsynjs .
Nếu chức năng cơ bản được quảng bá
nsynjs sẽ đánh giá tuần tự tất cả các lời hứa và đưa kết quả lời hứa vào thuộc data
tính:
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>
Nếu chức năng cơ bản không được quảng bá
Bước 1. Gói hàm với callback vào trình bao bọc nhận biết nsynjs (nếu nó có phiên bản quảng bá, bạn có thể bỏ qua bước này):
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;
Bước 2. Đặt logic đồng bộ vào chức năng:
function process() {
console.log('got data:', ajaxGet(nsynjsCtx, "data/file1.json").data);
}
Bước 3. Chạy chức năng một cách đồng bộ qua nsynjs:
nsynjs.run(process,this,function () {
console.log("synchronous function finished");
});
Nsynjs sẽ đánh giá tất cả các toán tử và biểu thức theo từng bước, tạm dừng thực thi trong trường hợp nếu kết quả của một số chức năng chậm chưa sẵn sàng.
Các ví dụ khác tại đây: https://github.com/amaksr/nsynjs/tree/master/examples
ECMAScript 6 có 'trình tạo' cho phép bạn dễ dàng lập trình theo kiểu không đồng bộ.
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();
}
}
Để chạy đoạn mã trên, bạn làm như sau:
const gen = myGenerator(); // Create generator
gen.next(); // Start it
gen.next((...args) => gen.next([...args])); // Set its callback function
Nếu bạn cần nhắm mục tiêu các trình duyệt không hỗ trợ ES6, bạn có thể chạy mã thông qua Babel hoặc trình biên dịch đóng để tạo ECMAScript 5.
Lệnh gọi lại ...args
được bao bọc trong một mảng và bị hủy khi bạn đọc chúng để mẫu có thể đối phó với các lệnh gọi lại có nhiều đối số. Ví dụ với nút fs :
const [err, data] = yield fs.readFile(filePath, "utf-8", callback);
Dưới đây là một số cách tiếp cận để làm việc với các yêu cầu không đồng bộ:
- Đối tượng Lời hứa trình duyệt
- Q - Thư viện hứa hẹn dành cho JavaScript
- A + Promises.js
- jQuery bị hoãn lại
- API XMLHttpRequest
- Sử dụng khái niệm gọi lại - Như triển khai trong câu trả lời đầu tiên
Ví dụ: triển khai jQuery trì hoãn để làm việc với nhiều yêu cầu
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();
Chúng ta thấy mình đang ở trong một vũ trụ dường như đang tiến triển dọc theo một chiều mà chúng ta gọi là "thời gian". Chúng tôi không thực sự hiểu thời gian là gì, nhưng chúng tôi đã phát triển các từ vựng và khái niệm trừu tượng cho phép chúng tôi suy luận và nói về nó: "quá khứ", "hiện tại", "tương lai", "trước", "sau".
Các hệ thống máy tính mà chúng tôi xây dựng - ngày càng nhiều - có thời gian là một thứ quan trọng. Một số điều chắc chắn sẽ xảy ra trong tương lai. Sau đó, những thứ khác cần phải xảy ra sau khi những điều đầu tiên đó cuối cùng xảy ra. Đây là khái niệm cơ bản được gọi là "tính không đồng bộ". Trong thế giới ngày càng được nối mạng của chúng ta, trường hợp không đồng bộ phổ biến nhất là chờ một hệ thống từ xa nào đó phản hồi một số yêu cầu.
Hãy xem xét một ví dụ. Bạn gọi người bán sữa và gọi một ít sữa. Khi nó đến, bạn muốn cho nó vào cà phê của mình. Bạn không thể cho sữa vào cà phê của mình ngay bây giờ vì nó chưa có ở đây. Bạn phải đợi nó đến trước khi cho nó vào cà phê của bạn. Nói cách khác, những điều sau sẽ không hoạt động:
var milk = order_milk();
put_in_coffee(milk);
Bởi vì JS không có cách nào để biết rằng nó cần phải chờ đợi cho order_milk
đến khi kết thúc trước khi nó thực thi put_in_coffee
. Nói cách khác, nó không biết rằng đó order_milk
là không đồng bộ - là thứ sẽ không tạo ra sữa cho đến một thời điểm nào đó trong tương lai. JS và các ngôn ngữ khai báo khác thực hiện hết câu lệnh này đến câu lệnh khác mà không cần chờ đợi.
Cách tiếp cận JS cổ điển cho vấn đề này, tận dụng lợi thế của thực tế là JS hỗ trợ các hàm như các đối tượng lớp đầu tiên có thể được truyền xung quanh, là truyền một hàm dưới dạng tham số cho yêu cầu không đồng bộ, sau đó nó sẽ gọi khi hoàn thành nhiệm vụ của nó đôi khi trong tương lai. Đó là cách tiếp cận "gọi lại". Nó trông như thế này:
order_milk(put_in_coffee);
order_milk
bắt đầu, đặt hàng sữa, sau đó, khi và chỉ khi nó đến, nó gọi put_in_coffee
.
Vấn đề với phương pháp gọi lại này là nó gây ô nhiễm ngữ nghĩa bình thường của một hàm báo cáo kết quả của nó với return
; thay vào đó, các hàm không được báo cáo kết quả của chúng bằng cách gọi một lệnh gọi lại đã cho dưới dạng tham số. Ngoài ra, cách tiếp cận này có thể nhanh chóng trở nên khó sử dụng khi xử lý các chuỗi sự kiện dài hơn. Ví dụ: giả sử tôi muốn đợi sữa được cho vào cà phê, sau đó và chỉ sau đó thực hiện bước thứ ba, đó là uống cà phê. Cuối cùng tôi cần phải viết một cái gì đó như thế này:
order_milk(function(milk) { put_in_coffee(milk, drink_coffee); }
nơi tôi đang chuyển tới put_in_coffee
cả sữa để cho vào nó và cả hành động ( drink_coffee
) để thực thi khi sữa đã được đưa vào. Đoạn mã như vậy trở nên khó viết, đọc và gỡ lỗi.
Trong trường hợp này, chúng tôi có thể viết lại mã trong câu hỏi dưới dạng:
var answer;
$.ajax('/foo.json') . done(function(response) {
callback(response.data);
});
function callback(data) {
console.log(data);
}
Nhập lời hứa
Đây là động lực cho khái niệm "lời hứa", là một loại giá trị cụ thể đại diện cho một số loại kết quả tương lai hoặc không đồng bộ . Nó có thể đại diện cho điều gì đó đã xảy ra, hoặc điều đó sẽ xảy ra trong tương lai, hoặc có thể không bao giờ xảy ra. Các lời hứa có một phương thức duy nhất, được đặt tên then
, mà bạn chuyển một hành động sẽ được thực hiện khi kết quả mà lời hứa đại diện đã được thực hiện.
Trong trường hợp sữa và cà phê của chúng tôi, chúng tôi thiết kế order_milk
để gửi lại một lời hứa cho sữa đến, sau đó chỉ định put_in_coffee
như một then
hành động, như sau:
order_milk() . then(put_in_coffee)
Một lợi thế của điều này là chúng ta có thể xâu chuỗi những thứ này lại với nhau để tạo chuỗi các lần xuất hiện trong tương lai ("chuỗi"):
order_milk() . then(put_in_coffee) . then(drink_coffee)
Hãy áp dụng những lời hứa cho vấn đề cụ thể của bạn. Chúng tôi sẽ bọc logic yêu cầu của mình bên trong một hàm, hàm này trả về một lời hứa:
function get_data() {
return $.ajax('/foo.json');
}
Trên thực tế, tất cả những gì chúng tôi đã làm là thêm a return
vào cuộc gọi tới $.ajax
. Điều này hoạt động vì jQuery $.ajax
đã trả về một thứ giống như lời hứa. (Trong thực tế, nếu không đi sâu vào chi tiết, chúng tôi muốn kết thúc cuộc gọi này để trả lại một lời hứa thực sự hoặc sử dụng một số thay thế cho $.ajax
điều đó.) Bây giờ, nếu chúng ta muốn tải tệp và đợi nó kết thúc và sau đó làm điều gì đó, chúng ta chỉ có thể nói
get_data() . then(do_something)
ví dụ,
get_data() .
then(function(data) { console.log(data); });
Khi sử dụng các hứa hẹn, chúng ta sẽ chuyển nhiều hàm vào then
, vì vậy, việc sử dụng các hàm mũi tên kiểu ES6 nhỏ gọn hơn thường hữu ích:
get_data() .
then(data => console.log(data));
các async
từ khóa
Nhưng vẫn có điều gì đó mơ hồ không hài lòng về việc phải viết mã một cách nếu đồng bộ và một cách khá khác nếu không đồng bộ. Để đồng bộ, chúng tôi viết
a();
b();
nhưng nếu a
là không đồng bộ, với các lời hứa, chúng ta phải viết
a() . then(b);
Ở trên, chúng tôi đã nói, "JS không có cách nào để biết rằng nó cần phải đợi cuộc gọi đầu tiên kết thúc trước khi thực hiện cuộc gọi thứ hai". Nó sẽ không được tốt đẹp nếu có là một số cách để nói với JS đó? Hóa ra là có - await
từ khóa, được sử dụng bên trong một loại hàm đặc biệt được gọi là hàm "async". Tính năng này là một phần của phiên bản sắp tới của ES nhưng đã có sẵn trong các bộ chuyển đổi như Babel với các cài đặt trước phù hợp. Điều này cho phép chúng tôi viết đơn giản
async function morning_routine() {
var milk = await order_milk();
var coffee = await put_in_coffee(milk);
await drink(coffee);
}
Trong trường hợp của bạn, bạn có thể viết một cái gì đó như
async function foo() {
data = await get_data();
console.log(data);
}
Câu trả lời ngắn gọn : foo()
Phương thức của bạn trả về ngay lập tức, trong khi lệnh $ajax()
gọi thực thi không đồng bộ sau khi hàm trả về . Sau đó, vấn đề là làm thế nào hoặc ở đâu để lưu trữ các kết quả được truy xuất bởi lệnh gọi không đồng bộ khi nó trả về.
Một số giải pháp đã được đưa ra trong chủ đề này. Có lẽ cách dễ nhất là truyền một đối tượng cho foo()
phương thức và lưu trữ kết quả trong một thành viên của đối tượng đó sau khi hoàn tất lệnh gọi không đồng bộ.
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
Lưu ý rằng cuộc gọi tới foo()
vẫn sẽ không trả về kết quả nào hữu ích. Tuy nhiên, kết quả của cuộc gọi không đồng bộ bây giờ sẽ được lưu trữ trong result.response
.
Sử dụng một callback()
chức năng bên trong foo()
thành công. Hãy thử theo cách này. Nó là đơn giản và dễ hiểu.
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();
Sử dụng Lời hứa
Câu trả lời hoàn hảo nhất cho câu hỏi này là sử dụng 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);
});
}
Sử dụng
ajax("GET", "/test", "acrive=1").then(function(result) {
// Code depending on result
})
.catch(function() {
// An error occurred
});
Nhưng chờ đã ...!
Có một vấn đề với việc sử dụng lời hứa!
Tại sao chúng ta nên sử dụng Lời hứa tùy chỉnh của riêng mình?
Tôi đã sử dụng giải pháp này một thời gian cho đến khi tôi phát hiện ra có lỗi trong các trình duyệt cũ:
Uncaught ReferenceError: Promise is not defined
Vì vậy, tôi đã quyết định triển khai lớp Promise của riêng mình cho các trình biên dịch ES3 đến bên dưới js nếu nó không được định nghĩa. Chỉ cần thêm mã này trước mã chính của bạn và sau đó sử dụng Promise một cách an toàn!
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;
}();
}
Câu hỏi là:
Làm cách nào để trả lại phản hồi từ cuộc gọi không đồng bộ?
có thể được hiểu là:
Làm thế nào để làm cho mã không đồng bộ trông đồng bộ ?
Giải pháp sẽ là tránh các cuộc gọi lại và sử dụng kết hợp Promises và async / await .
Tôi muốn đưa ra một ví dụ cho một yêu cầu Ajax.
(Mặc dù nó có thể được viết bằng Javascript, nhưng tôi thích viết nó bằng Python và biên dịch nó sang Javascript bằng Transcrypt . Nó sẽ đủ rõ ràng.)
Trước tiên hãy cho phép kích hoạt sử dụng JQuery, có $
sẵn như S
:
__pragma__ ('alias', 'S', '$')
Xác định một hàm trả về một Lời hứa , trong trường hợp này là một lệnh gọi 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()
Sử dụng mã không đồng bộ như thể nó đồng bộ :
async def readALot():
try:
result1 = await read("url_1")
result2 = await read("url_2")
except Exception:
console.warn("Reading a lot failed")
Tất nhiên có nhiều cách tiếp cận như yêu cầu đồng bộ, lời hứa, nhưng theo kinh nghiệm của tôi, tôi nghĩ bạn nên sử dụng cách tiếp cận gọi lại. Hành vi không đồng bộ của Javascript là điều tự nhiên. Vì vậy, đoạn mã của bạn có thể được viết lại hơi khác một chút:
function foo() {
var result;
$.ajax({
url: '...',
success: function(response) {
myCallback(response);
}
});
return result;
}
function myCallback(response) {
// Does something.
}
Sau khi đọc tất cả các câu trả lời ở đây và với kinh nghiệm của mình, tôi muốn tiếp tục chi tiết về
callback, promise and async/await
lập trình không đồng bộ trong JavaScript.
1) Gọi lại: Lý do cơ bản cho việc gọi lại là chạy mã để phản hồi một sự kiện (xem ví dụ bên dưới). Chúng tôi sử dụng callback trong JavaScript mọi lúc.
const body = document.getElementsByTagName('body')[0];
function callback() {
console.log('Hello');
}
body.addEventListener('click', callback);
Nhưng nếu bạn phải sử dụng nhiều lệnh gọi lại lồng nhau trong ví dụ dưới đây, sẽ rất khủng khiếp cho việc tái cấu trúc mã.
asyncCallOne(function callback1() {
asyncCallTwo(function callback2() {
asyncCallThree(function callback3() {
...
})
})
})
2) Promise: một cú pháp ES6 - Promise giải quyết vấn đề địa ngục gọi lại!
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 là một phiên bản Promise đại diện cho quá trình mã không đồng bộ. Hàm giải quyết báo hiệu rằng phiên bản Promise đã kết thúc. Sau đó, chúng ta có thể gọi .then () (một chuỗi .then như bạn muốn) và .catch () trên cá thể lời hứa:
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: một cú pháp mới ES6 - Await về cơ bản là cú pháp đường của Promise!
Hàm async cung cấp cho chúng ta một cú pháp ngắn gọn và rõ ràng cho phép chúng ta viết ít mã hơn để đạt được kết quả giống như những gì chúng ta sẽ nhận được với những lời hứa. Async / Await trông tương tự như mã đồng bộ và mã đồng bộ dễ đọc và viết hơn nhiều. Để bắt lỗi với Async / Await, chúng ta có thể sử dụng khối try...catch
. Ở đây, bạn không cần phải viết chuỗi .then () của cú pháp 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();
Kết luận: Đây hoàn toàn là ba cú pháp cho lập trình không đồng bộ trong JavaScript mà bạn chắc chắn phải hiểu rõ. Vì vậy, nếu có thể, tôi khuyên bạn nên sử dụng "promise" hoặc "async / await" để cấu trúc lại mã không đồng bộ của mình (chủ yếu là cho các yêu cầu XHR) !
Thay vì ném mã vào bạn, có 2 khái niệm quan trọng để hiểu cách JS xử lý lệnh gọi lại và tính không đồng bộ. (Ngay cả là một từ?)
Vòng lặp sự kiện và mô hình đồng tiền
Có ba điều bạn cần lưu ý; Hàng đợi; vòng lặp sự kiện và ngăn xếp
Nói một cách đơn giản, rộng rãi, vòng lặp sự kiện giống như trình quản lý dự án, nó liên tục lắng nghe bất kỳ chức năng nào muốn chạy và giao tiếp giữa hàng đợi và ngăn xếp.
while (queue.waitForMessage()) {
queue.processNextMessage();
}
Khi nó nhận được một thông báo để chạy một cái gì đó, nó sẽ thêm nó vào hàng đợi. Hàng đợi là danh sách những thứ đang chờ thực thi (như yêu cầu AJAX của bạn). hãy tưởng tượng nó như thế này:
1. call foo.com/api/bar using foobarFunc
2. Go perform an infinite loop
... and so on
Khi một trong những thông báo này sẽ thực thi, nó sẽ bật thông báo từ hàng đợi và tạo một ngăn xếp, ngăn xếp là mọi thứ JS cần thực thi để thực hiện lệnh trong thông báo. Vì vậy, trong ví dụ của chúng tôi, nó được yêu cầu gọifoobarFunc
function foobarFunc (var) {
console.log(anotherFunction(var));
}
Vì vậy, bất kỳ thứ gì foobarFunc cần thực thi (trong trường hợp của chúng tôi anotherFunction
) sẽ được đẩy lên ngăn xếp. được thực thi, và sau đó bị quên - vòng lặp sự kiện sau đó sẽ chuyển sang điều tiếp theo trong hàng đợi (hoặc lắng nghe thông báo)
Điều quan trọng ở đây là thứ tự thực hiện. Đó là
KHI nào thứ gì đó sẽ chạy
Khi bạn thực hiện cuộc gọi bằng AJAX cho bên ngoài hoặc chạy bất kỳ mã không đồng bộ nào (ví dụ: setTimeout), Javascript phụ thuộc vào phản hồi trước khi có thể tiếp tục.
Câu hỏi lớn là khi nào nó sẽ nhận được phản hồi? Câu trả lời là chúng tôi không biết - vì vậy vòng lặp sự kiện đang chờ thông báo đó nói "hey run me". Nếu JS chỉ đợi thông báo đó một cách đồng bộ, ứng dụng của bạn sẽ bị đóng băng và nó sẽ rất tệ. Vì vậy, JS tiếp tục thực thi mục tiếp theo trong hàng đợi trong khi chờ thông báo được thêm trở lại hàng đợi.
Đó là lý do tại sao với chức năng không đồng bộ, chúng tôi sử dụng những thứ được gọi là gọi lại . Nó giống như một lời hứa theo nghĩa đen. Như trong tôi hứa sẽ trả lại một cái gì đó tại một số thời điểm jQuery sử dụng các lệnh gọi lại cụ thể được gọi deffered.done
deffered.fail
và deffered.always
(trong số những người khác). Bạn có thể xem tất cả ở đây
Vì vậy, những gì bạn cần làm là chuyển một hàm được hứa sẽ thực thi tại một thời điểm nào đó với dữ liệu được truyền cho nó.
Bởi vì một lệnh gọi lại không được thực thi ngay lập tức nhưng sau đó, điều quan trọng là phải chuyển tham chiếu đến hàm chứ không phải hàm được thực thi. vì thế
function foo(bla) {
console.log(bla)
}
vì vậy hầu hết thời gian (nhưng không phải luôn luôn) bạn sẽ foo
không vượt quafoo()
Hy vọng rằng điều đó sẽ có ý nghĩa. Khi bạn gặp những thứ như thế này có vẻ khó hiểu - tôi thực sự khuyên bạn nên đọc tài liệu đầy đủ để ít nhất hiểu rõ về nó. Nó sẽ giúp bạn trở thành một nhà phát triển tốt hơn nhiều.