標籤:正是 fun 伺服器 red 判斷語句 called fine 有一個 回呼函數
先看這樣一個例子:
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 itemdoSomethingElse();
執行 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上這個話題。
關於非同步請求的一些事