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 MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...