關於非同步請求的一些事
先看這樣一個例子:
function foo() {
var result;
$.ajax({
url: '...',
success: function(response) {
result = response;
}
});
return result;
}
var result = foo(); // It always ends up being `undefined`.
在這個例子中,使用ajax進行非同步請求,但是響應的返回值始終是undefined。那這是什麼原因呢?這個問題最初也讓我很困惑。下面我們來分析一下原因。
找出問題的原因
我們知道ajax中的a代表非同步,那就意味著傳送請求(亦或是接受響應)需要花費一點時間。而在上述例子中,$.ajax
是被立即返回的,return result;
這句語句在success
函式返回響應前就已經被執行了。主要原因還是沒有理解非同步和同步之間的區別。
下面這個類比希望可以使同步和非同步的區別更清晰。
Synchronous(同步流)
想象你給你的一個朋友打電話,你希望他可以幫你一個忙,你會進行一段時間的等待,直到他的回答。
那麼在正常同步流的程式碼中也會發生一樣的事情。
function findItem() { var item; while(item_not_found) { // search } return item; } var item = findItem(); // Do something with item doSomethingElse();
執行 findItem
需要花費一段時間,所有在 var item = findItem();
這條語句之後的程式碼都需要等待函式返回結果。
Asynchronous(非同步流)
同樣你又給你的朋友打了一個電話,但是現在你很忙,你給你的朋友留言讓他給你回一個電話,然後你掛掉電話就做其他的事情了,直到你的朋友回你電話,你才會停下手中的事情處理這個電話。
這也正是ajax請求的過程。
findItem(function(item) {
// Do something with item
});
doSomethingElse();
不去等待響應而是立即繼續執行ajax之後的語句。而為了得到響應我們會定義一個回撥函式,一旦收到響應就會執行這個回撥函式。但是注意在ajax請求之後的語句會在回撥函式呼叫前被執行。
解決問題的方法
1. 使用回撥函式
上面的例子中
var result = foo();
// Code that depends on 'result'
改為
foo(function(result) {
// Code that depends on 'result'
});
然後我們給 foo
傳遞函式引數
function myCallback(result) {
// Code that depends on 'result'
}
foo(myCallback);
函式 foo
定義如下
function foo(callback) {
$.ajax({
// ...
success: callback
});
}
一旦ajax
請求成功,$.ajax
就會呼叫callback
函式並且將響應傳遞給callback
函式。
2.使用promise
Promise API是ES6新特性之一,但是各大瀏覽器已經有了很好的支援(IE11及以上支援)。MDN這麼定義它:
Promise 物件用於非同步計算。一個 Promise 表示一個現在、將來或永不可能可用的值
promise的語法:
new Promise(
/* executor */
function(resolve, reject) {...}
);
這裡是一段使用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);
});
}
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
});
這裡有關於promise
的更多資訊。
3.jQuey: 使用延遲函式
jQery的延遲函式和promise
很像,但是API略有不同。每一個jQery的ajax方法都會返回一個延遲函式。
function ajax() {
return $.ajax(...);
}
ajax().done(function(result) {
// Code depending on result
}).fail(function() {
// An error occurred
});
始終要牢記promise
和延遲函式都只有一個存有未來值 的容器而不是值本身。看下面的例子:
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()
會向伺服器傳送對/password
的請求,jQery內部機制會立即會返回一個ajax延遲函式,而這個並不是伺服器發回來的響應。這也就意味著下面的判斷語句(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()
也會立即返回一個延遲物件,但是我們通過監聽.done()
和.fail()
事件能正確處理伺服器返回的響應。.done()
事件被呼叫時,伺服器返回的是一個正常的響應(http 200),我們通過檢查他的返回物件是否為true判斷使用者是否登陸。
.fail()
處理函式是處理一些錯誤的事件。例如使用者網路斷開或伺服器錯誤等。
本文程式碼均來自StackOverflow上這個話題。