JavaScript Ajax + Promise

来源:http://www.cnblogs.com/284628487a/archive/2016/06/03/5556144.html
-Advertisement-
Play Games

AJAX 在現代瀏覽器上寫AJAX主要依靠XMLHttpRequest對象: function success(text) { var textarea = document.getElementById('test-response-text'); textarea.value = text; } ...


AJAX

在現代瀏覽器上寫AJAX主要依靠XMLHttpRequest對象:

function success(text) {
   var textarea = document.getElementById('test-response-text');
   textarea.value = text;
}
function fail(code) {
   var textarea = document.getElementById('test-response-text');
   textarea.value = 'Error code: ' + code;
}
var request = new XMLHttpRequest(); // 新建XMLHttpRequest對象
request.onreadystatechange = function () { // 狀態發生變化時,函數被回調
   if (request.readyState === 4) { // 成功完成
       // 判斷響應結果:
       if (request.status === 200) {
           // 成功,通過responseText拿到響應的文本:
           return success(request.responseText);
       } else {
           // 失敗,根據響應碼判斷失敗原因:
           return fail(request.status);
       }
   } else {
       // HTTP請求還在繼續...
   }
}
// 發送請求:
request.open('GET', '/api/categories');
request.send();
alert('請求已發送,請等待響應...');

如果想把標準寫法和IE寫法混在一起,可以這麼寫:

var request;
if (window.XMLHttpRequest) {
    request = new XMLHttpRequest();
} else {
    request = new ActiveXObject('Microsoft.XMLHTTP');
}

通過檢測window對象是否有XMLHttpRequest屬性來確定瀏覽器是否支持標準的XMLHttpRequest。註意,不要根據瀏覽器的navigator.userAgent來檢測瀏覽器是否支持某個JavaScript特性,一是因為這個字元串本身可以偽造,二是通過IE版本判斷JavaScript特性將非常複雜。

當創建了XMLHttpRequest對象後,要先設置onreadystatechange的回調函數。在回調函數中,通常我們只需通過readyState === 4判斷請求是否完成,如果已完成,再根據status === 200判斷是否是一個成功的響應。

XMLHttpRequest對象的open()方法有3個參數,第一個參數指定是GET還是POST,第二個參數指定URL地址,第三個參數指定是否使用非同步,預設是true,所以不用寫。註意,千萬不要把第三個參數指定為false,否則瀏覽器將停止響應,直到AJAX請求完成。 最後調用send()方法才真正發送請求。GET請求不需要參數,POST請求需要把body部分以字元串或者FormData對象傳進去。

安全限制JSONP

預設情況下,JavaScript在發送AJAX請求時,URL的功能變數名稱必須和當前頁面完全一致。

完全一致的意思是,功能變數名稱要相同 (www.example.comexample.com不同) ,協議要相同(httphttps不同),埠號要相同(預設是:80埠,它和:8080就不同)。有的瀏覽器口子松一點,允許埠不同,大多數瀏覽器都會嚴格遵守這個限制。

JSONP,它有個限制,只能用GET請求,並且要求返回JavaScript。這種方式跨域實際上是利用了瀏覽器允許跨域引用JavaScript資源:

<html><head>
    <script src="http://example.com/abc.js"></script>
    ...</head><body>...</body></html>

JSONP通常以函數調用的形式返回,例如,返回JavaScript內容如下:

foo('data');

這樣一來,我們如果在頁面中先準備好foo()函數,然後給頁面動態加一個<script>節點,相當於動態讀取外域的JavaScript資源,最後就等著接收回調了。

以163的股票查詢URL為例,對於URL:http://api.money.126.net/data/feed/0000001,1399001?callback=refreshPrice,你將得到如下返回:

refreshPrice({"0000001":{"code": "0000001", ... });

因此我們需要首先在頁面中準備好回調函數:

function refreshPrice(data) {
    var p = document.getElementById('test-jsonp');
    p.innerHTML = '當前價格:' +
        data['0000001'].name +': ' + 
        data['0000001'].price + ';' +
        data['1399001'].name + ': ' +
        data['1399001'].price;
}

最後用getPrice()函數觸發:

function getPrice() {
    var
        js = document.createElement('script'),
        head = document.getElementsByTagName('head')[0];
    js.src = 'http://api.money.126.net/data/feed/0000001,1399001?callback=refreshPrice';
    head.appendChild(js);
}

就完成了跨域載入數據。

CORS

如果瀏覽器支持HTML5,那麼就可以一勞永逸地使用新的跨域策略:CORS了。

CORS全稱Cross-Origin Resource Sharing,是HTML5規範定義的如何跨域訪問資源。

瞭解CORS前,我們先搞明白概念:

Origin表示本域,也就是瀏覽器當前頁面的域。當JavaScript向外域(如sina.com)發起請求後,瀏覽器收到響應後,首先檢查Access-Control-Allow-Origin是否包含本域,如果是,則此次跨域請求成功,如果不是,則請求失敗,JavaScript將無法獲取到響應的任何數據。

用一個圖來表示就是:

假設本域是my.com,外域是sina.com,只要響應頭Access-Control-Allow-Originhttp://my.com,或者是*,本次請求就可以成功。

可見,跨域能否成功,取決於對方伺服器是否願意給你設置一個正確的Access-Control-Allow-Origin,決定權始終在對方手中。

上面這種跨域請求,稱之為“簡單請求”。簡單請求包括GET、HEAD和POST(POST的Content-Type類型 僅限application/x-www-form-urlencodedmultipart/form-datatext/plain),並且不能出現任何自定義頭(例如,X-Custom: 12345),通常能滿足90%的需求。

無論你是否需要用JavaScript通過CORS跨域請求資源,你都要瞭解CORS的原理。最新的瀏覽器全面支持HTML5。在引用外域資源時,除了JavaScript和CSS外,都要驗證CORS。例如,當你引用了某個第三方CDN上的字體文件時:

/* CSS */@font-face {  font-family: 'FontAwesome';
  src: url('http://cdn.com/fonts/fontawesome.ttf') format('truetype');}

如果該CDN服務商未正確設置Access-Control-Allow-Origin,那麼瀏覽器無法載入字體資源。

對於PUT、DELETE以及其他類型如application/json的POST請求,在發送AJAX請求之前,瀏覽器會先發送一個OPTIONS請求(稱為preflighted請求)到這個URL上,詢問目標伺服器是否接受:

OPTIONS /path/to/resource HTTP/1.1
Host: bar.com
Origin: http://my.com
Access-Control-Request-Method: POST

伺服器必須響應並明確指出允許的Method:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://my.com
Access-Control-Allow-Methods: POST, GET, PUT, OPTIONS
Access-Control-Max-Age: 86400

瀏覽器確認伺服器響應的Access-Control-Allow-Methods頭確實包含將要發送的AJAX請求的Method,才會繼續發送AJAX,否則,拋出一個錯誤。

由於以POSTPUT方式傳送JSON格式的數據在REST中很常見,所以要跨域正確處理POSTPUT請求,伺服器端必須正確響應OPTIONS請求。

Promise

在JavaScript的世界中,所有代碼都是單線程執行的。

由於這個“缺陷”,導致JavaScript的所有網路操作,瀏覽器事件,都必須是非同步執行。非同步執行可以用回調函數實現:

function callback() {
    console.log('Done');
}
console.log('before setTimeout()');
setTimeout(callback, 1000); // 1秒鐘後調用callback函數console.log('after setTimeout()');

觀察上述代碼執行,在Chrome的控制台輸出可以看到:

before setTimeout()
after setTimeout()
(等待1秒後)
Done

可見,非同步操作會在將來的某個時間點觸發一個函數調用。

AJAX就是典型的非同步操作。以上一節的代碼為例:

request.onreadystatechange = function () {
    if (request.readyState === 4) {
        if (request.status === 200) {
            return success(request.responseText);
        } else {
            return fail(request.status);
        }
    }
}

把回調函數success(request.responseText)fail(request.status)寫到一個AJAX操作里很正常,但是不好看,而且不利於代碼復用。

有沒有更好的寫法?比如寫成這樣:

var ajax = ajaxGet('http://...');
ajax.ifSuccess(success)
    .ifFail(fail);

這種鏈式寫法的好處在於,先統一執行AJAX邏輯,不關心如何處理結果,然後,根據結果是成功還是失敗,在將來的某個時候調用success函數或fail函數。

古人雲:“君子一諾千金”,這種“承諾將來會執行”的對象在JavaScript中稱為Promise對象。

Promise有各種開源實現,在ES6中被統一規範,由瀏覽器直接支持。

new Promise(function () {});
alert("支持Promise");

先看一個最簡單的Promise例子:生成一個0-2之間的隨機數,如果小於1,則等待一段時間後返回成功,否則返回失敗:

function test(resolve, reject) {
    var timeOut = Math.random() * 2;
    log('set timeout to: ' + timeOut + ' seconds.');
    setTimeout(function () {
        if (timeOut < 1) {
            log('call resolve()...');
            resolve('200 OK');
        } else {
            log('call reject()...');
            reject('timeout in ' + timeOut + ' seconds.');
        }
    }, timeOut * 1000);
}

這個test()函數有兩個參數,這兩個參數都是函數,如果執行成功,我們將調用resolve('200 OK'),如果執行失敗,我們將調用reject('timeout in ' + timeOut + ' seconds.')。可以看出,test()函數只關心自身的邏輯,並不關心具體的resolvereject將如何處理結果。

有了執行函數,我們就可以用一個Promise對象來執行它,併在將來某個時刻獲得成功或失敗的結果:

var p1 = new Promise(test);
var p2 = p1.then(function (result) {
    console.log('成功:' + result);
});
var p3 = p2.catch(function (reason) {
    console.log('失敗:' + reason);
});

變數p1是一個Promise對象,它負責執行test函數。由於test函數在內部是非同步執行的,當test函數執行成功時,我們告訴Promise對象:

// 如果成功,執行這個函數:
p1.then(function (result) {
    console.log('成功:' + result);
});

test函數執行失敗時,我們告訴Promise對象:

p2.catch(function (reason) {
    console.log('失敗:' + reason);
});

Promise對象可以串聯起來,所以上述代碼可以簡化為:

new Promise(test).then(function (result) {
    console.log('成功:' + result);
}).catch(function (reason) {
    console.log('失敗:' + reason);
});

實際測試一下,看看Promise是如何非同步執行的:

'use strict';

// 清除log:
var logging = document.getElementById('test-promise-log');
while (logging.children.length > 1) {
    logging.removeChild(logging.children[logging.children.length - 1]);
}

// 輸出log到頁面:
function log(s) {
    var p = document.createElement('p');
    p.innerHTML = s;
    logging.appendChild(p);
}

new Promise(function (resolve, reject) {
    log('start new Promise...');
    var timeOut = Math.random() * 2;
    log('set timeout to: ' + timeOut + ' seconds.');
    setTimeout(function () {
        if (timeOut < 1) {
            log('call resolve()...');
            resolve('200 OK');
        }
        else {
            log('call reject()...');
            reject('timeout in ' + timeOut + ' seconds.');
        }
    }, timeOut * 1000);
}).then(function (r) {
    log('Done: ' + r);
}).catch(function (reason) {
    log('Failed: ' + reason);
});

輸出結果為:

start new Promise...

set timeout to: 1.7587399336588674 seconds.

call reject()...

Failed: timeout in 1.7587399336588674 seconds.

可見Promise最大的好處是在非同步執行的流程中,把執行代碼和處理結果的代碼清晰地分離了:

 

 

Promise還可以做更多的事情,比如,有若幹個非同步任務,需要先做任務1,如果成功後再做任務2,任何任務失敗則不再繼續並執行錯誤處理函數。

要串列執行這樣的非同步任務,不用Promise需要寫一層一層的嵌套代碼。有了Promise,我們只需要簡單地寫:

job1.then(job2).then(job3).catch(handleError);

其中,job1job2job3都是Promise對象。

 

下麵的例子演示瞭如何串列執行一系列需要非同步計算獲得結果的任務:

 
var logging = document.getElementById('test-promise2-log');
while (logging.children.length > 1) {
    logging.removeChild(logging.children[logging.children.length - 1]);
}

function log(s) {
    var p = document.createElement('p');
    p.innerHTML = s;
    logging.appendChild(p);
}

// 0.5秒後返回input*input的計算結果:
function multiply(input) {
    return new Promise(function (resolve, reject) {
        log('calculating ' + input + ' x ' + input + '...');
        setTimeout(resolve, 500, input * input);
    });
}

// 0.5秒後返回input+input的計算結果:
function add(input) {
    return new Promise(function (resolve, reject) {
        log('calculating ' + input + ' + ' + input + '...');
        setTimeout(resolve, 500, input + input);
    });
}

var p = new Promise(function (resolve, reject) {
    log('start new Promise...');
    resolve(123);
});

p.then(multiply)
 .then(add)
 .then(multiply)
 .then(add)
 .then(function (result) {
    log('Got value: ' + result);
});

start new Promise...

calculating 123 x 123...

calculating 15129 + 15129...

calculating 30258 x 30258...

calculating 915546564 + 915546564...

Got value: 1831093128

setTimeout可以看成一個模擬網路等非同步執行的函數。現在,我們把上一節的AJAX非同步執行函數轉換為Promise對象,看看用Promise如何簡化非同步處理:

// ajax函數將返回Promise對象:
function ajax(method, url, data) {
    var request = new XMLHttpRequest();
    return new Promise(function (resolve, reject) {
        request.onreadystatechange = function () {
            if (request.readyState === 4) {
                if (request.status === 200) {
                    resolve(request.responseText);
                } else {
                    reject(request.status);
                }
            }
        };
        request.open(method, url);
        request.send(data);
    });
}

var log = document.getElementById('test-promise-ajax-result');
var p = ajax('GET', '/api/categories');
p.then(function (text) { // 如果AJAX成功,獲得響應內容
    log.innerText = text;
}).catch(function (status) { // 如果AJAX失敗,獲得響應代碼
    log.innerText = 'ERROR: ' + status;
});

除了串列執行若幹非同步任務外,Promise還可以並行執行非同步任務。

試想一個頁面聊天系統,我們需要從兩個不同的URL分別獲得用戶的個人信息和好友列表,這兩個任務是可以並行執行的,用Promise.all()實現如下:

var p1 = new Promise(function (resolve, reject) {
    setTimeout(resolve, 500, 'P1');
});
var p2 = new Promise(function (resolve, reject) { setTimeout(resolve, 600, 'P2'); }); // 同時執行p1和p2,併在它們都完成後執行then: Promise.all([p1, p2]).then(function (results) { console.log(results); // 獲得一個Array: ['P1', 'P2'] });

有些時候,多個非同步任務是為了容錯。比如,同時向兩個URL讀取用戶的個人信息,只需要獲得先返回的結果即可。這種情況下,用Promise.race()實現:

var p1 = new Promise(function (resolve, reject) {
    setTimeout(resolve, 500, 'P1');
});
var p2 = new Promise(function (resolve, reject) { setTimeout(resolve, 600, 'P2'); }); Promise.race([p1, p2]).then(function (result) { console.log(result); // 'P1' });

由於p1執行較快,Promise的then()將獲得結果'P1'p2仍在繼續執行,但執行結果將被丟棄。

如果我們組合使用Promise,就可以把很多非同步任務以並行和串列的方式組合起來執行。 

jQuery ajax

jQuery在全局對象jQuery(也就是$)綁定了ajax()函數,可以處理AJAX請求。ajax(url, settings)函數需要接收一個URL和一個可選的settings對象,常用的選項如下:

  • async:是否非同步執行AJAX請求,預設為true,千萬不要指定為false

  • method:發送的Method,預設為'GET',可指定為'POST''PUT'等;

  • contentType:發送POST請求的格式,預設值為'application/x-www-form-urlencoded; charset=UTF-8',也可以指定為text/plainapplication/json

  • data:發送的數據,可以是字元串、數組或object。如果是GET請求,data將被轉換成query附加到URL上,如果是POST請求,根據contentType把data序列化成合適的格式;

  • headers:發送的額外的HTTP頭,必須是一個object;

  • dataType:接收的數據格式,可以指定為'html''xml''json''text'等,預設情況下根據響應的Content-Type猜測。

下麵的例子發送一個GET請求,並返回一個JSON格式的數據:

var jqxhr = $.ajax('/api/categories', {
    dataType: 'json'
});// 請求已經發送了

不過,如何用回調函數處理返回的數據和出錯時的響應呢?

function ajaxLog(s) {
    var txt = $('#test-response-text');
    txt.val(txt.val() + '\n' + s);
}

$('#test-response-text').val('');

var jqxhr = $.ajax('/api/categories', {
    dataType: 'json'
}).done(function (data) {
    ajaxLog('成功, 收到的數據: ' + JSON.stringify(data));
}).fail(function (xhr, status) {
    ajaxLog('失敗: ' + xhr.status + ', 原因: ' + status);
}).always(function () {
    ajaxLog('請求完成: 無論成功或失敗都會調用');
});

get

對常用的AJAX操作,jQuery提供了一些輔助方法。由於GET請求最常見,所以jQuery提供了get()方法,可以這麼寫:

var jqxhr = $.get('/path/to/resource', {
name: 'Bob Lee', check: 1
});

第二個參數如果是object,jQuery自動把它變成query string然後加到URL後面,實際的URL是:

/path/to/resource?name=Bob%20Lee&check=1

這樣我們就不用關心如何用URL編碼並構造一個query string了。

post

post()get()類似,但是傳入的第二個參數預設被序列化為application/x-www-form-urlencoded

var jqxhr = $.post('/path/to/resource', {
name: 'Bob Lee',
check: 1
});

實際構造的數據name=Bob%20Lee&check=1作為POST的body被髮送。 

getJSON

由於JSON用得越來越普遍,所以jQuery也提供了getJSON()方法來快速通過GET獲取一個JSON對象:

var jqxhr = $.getJSON('/path/to/resource', {
    name: 'Bob Lee',
    check: 1}).done(function (data) {
    // data已經被解析為JSON對象了
});

安全限制

jQuery的AJAX完全封裝的是JavaScript的AJAX操作,所以它的安全限制和前面講的用JavaScript寫AJAX完全一樣。

如果需要使用JSONP,可以在ajax()中設置jsonp: 'callback',讓jQuery實現JSONP跨域載入數據。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 在傳統的Spring MVC開發方法中,必須在Bean配置文件中為每個控制器類配置實例和請求映射和讓每個控制器類去實現或者擴展特定於框架的介面或者基類,不夠靈活。 如果Spring MVC可以自動偵測你的控制器類和請求映射,就能減少配置所需要的工作量。 Spring2.5支持一種基於註解的控制器開發 ...
  • Java 項目中常常回遇到發送郵件 Java 發送郵件有幾種,今天先給大家介紹用 HtmlEmail 來發送郵件,我這裡是用 Maven 來搭建的 HtmlEmail 可以抄帶HTML 首先 需要導入jar 包 然後我們來建立一個發送郵件的 Mail 類 JavaBean 然後再來創建一個發送郵件的 ...
  • 只需要加android:windowSoftInputMode="stateHidden|stateAlwaysHidden"就可以 如:<activity android:name=".My_Message" android:windowSoftInputMode="stateHidden|sta ...
  • 一、fstat函數:顯示文件的所有信息 二、文件讀取: 三、寫入文件: 四、文件操作的應用: 五、拷貝文件: 六、創建文件 創建文件夾: 創建文件: 七、刪除文件: 刪除文件夾: 刪除文件: ...
  • COMMAND 模式 command模式非常簡單,簡單到你無法想象的地方。 這就是一個command模式的樣子。也許你會覺得,這有點多此一舉嗎。但是當你使用他的時候,command模式就會閃現光華。 這樣一個場景:經理張三叫leader王二去開發一個項目, 王二就安排李四 去開發這個功能A。 李四何 ...
  • CSS3可以做動畫大家肯定都是耳熟能詳的了,但是大家有木有巧妙的利用這一個功能來製作一款漂亮的照片牆呢? 那麼今天我們就利用CSS3動畫這一特性來一起製作漂亮的照片牆吧! 第一部分:HTML 這裡我們首先放十張圖片在頁面上面。(有什麼靚照儘管上來哦!) 第二部分:CSS3 這一部分就是我們這節的重點 ...
  • 昨天因為需要開始學習Pomelo 做H5游戲的服務端。 因為個人學習習慣,我從來不適合去跟著文檔看。一般我直接是看下大概的API,但是Pomelo的API全部都是英文的。 昨天我就告訴自己用一下午時間去做一個最基本的通信功能的DEMO。 安裝NODE.JS Phython VS 一切就緒之後就開始了 ...
  • 這篇文章是講解 Ioinc中怎麼實現 下拉刷新和上拉載入的。也是我們日常做項目是必不可少的功能。有興趣的小伙伴可以來學習一下。 更多關於 IONIC 的資源: http://www.aliyue.net/?s=ionic HTML部分 JS部分 on-refresh 下拉觸發的函數 函數執行結束之前 ...
一周排行
    -Advertisement-
    Play Games
  • 基於.NET Framework 4.8 開發的深度學習模型部署測試平臺,提供了YOLO框架的主流系列模型,包括YOLOv8~v9,以及其系列下的Det、Seg、Pose、Obb、Cls等應用場景,同時支持圖像與視頻檢測。模型部署引擎使用的是OpenVINO™、TensorRT、ONNX runti... ...
  • 十年沉澱,重啟開發之路 十年前,我沉浸在開發的海洋中,每日與代碼為伍,與演算法共舞。那時的我,滿懷激情,對技術的追求近乎狂熱。然而,隨著歲月的流逝,生活的忙碌逐漸占據了我的大部分時間,讓我無暇顧及技術的沉澱與積累。 十年間,我經歷了職業生涯的起伏和變遷。從初出茅廬的菜鳥到逐漸嶄露頭角的開發者,我見證了 ...
  • C# 是一種簡單、現代、面向對象和類型安全的編程語言。.NET 是由 Microsoft 創建的開發平臺,平臺包含了語言規範、工具、運行,支持開發各種應用,如Web、移動、桌面等。.NET框架有多個實現,如.NET Framework、.NET Core(及後續的.NET 5+版本),以及社區版本M... ...
  • 前言 本文介紹瞭如何使用三菱提供的MX Component插件實現對三菱PLC軟元件數據的讀寫,記錄了使用電腦模擬,模擬PLC,直至完成測試的詳細流程,並重點介紹了在這個過程中的易錯點,供參考。 用到的軟體: 1. PLC開發編程環境GX Works2,GX Works2下載鏈接 https:// ...
  • 前言 整理這個官方翻譯的系列,原因是網上大部分的 tomcat 版本比較舊,此版本為 v11 最新的版本。 開源項目 從零手寫實現 tomcat minicat 別稱【嗅虎】心有猛虎,輕嗅薔薇。 系列文章 web server apache tomcat11-01-官方文檔入門介紹 web serv ...
  • 1、jQuery介紹 jQuery是什麼 jQuery是一個快速、簡潔的JavaScript框架,是繼Prototype之後又一個優秀的JavaScript代碼庫(或JavaScript框架)。jQuery設計的宗旨是“write Less,Do More”,即倡導寫更少的代碼,做更多的事情。它封裝 ...
  • 前言 之前的文章把js引擎(aardio封裝庫) 微軟開源的js引擎(ChakraCore))寫好了,這篇文章整點js代碼來測一下bug。測試網站:https://fanyi.youdao.com/index.html#/ 逆向思路 逆向思路可以看有道翻譯js逆向(MD5加密,AES加密)附完整源碼 ...
  • 引言 現代的操作系統(Windows,Linux,Mac OS)等都可以同時打開多個軟體(任務),這些軟體在我們的感知上是同時運行的,例如我們可以一邊瀏覽網頁,一邊聽音樂。而CPU執行代碼同一時間只能執行一條,但即使我們的電腦是單核CPU也可以同時運行多個任務,如下圖所示,這是因為我們的 CPU 的 ...
  • 掌握使用Python進行文本英文統計的基本方法,並瞭解如何進一步優化和擴展這些方法,以應對更複雜的文本分析任務。 ...
  • 背景 Redis多數據源常見的場景: 分區數據處理:當數據量增長時,單個Redis實例可能無法處理所有的數據。通過使用多個Redis數據源,可以將數據分區存儲在不同的實例中,使得數據處理更加高效。 多租戶應用程式:對於多租戶應用程式,每個租戶可以擁有自己的Redis數據源,以確保數據隔離和安全性。 ...