很繞口的標題,不過就是我這次挑戰的需求。

用Google Map API轉換地址時,曾示範過利用$.when().then()等待所有$.ajax()呼叫都完成後才執行顯示地圖的jQuery寫法,但這次的情境有點不同。

  1. 每次處理1到多筆AJAX呼叫(透過$.post())
  2. 多筆AJAX呼叫需依序執行,第1筆執行完畢時才執行第2筆
  3. 當某一筆AJAX呼叫傳回特定結果時,代表出現狀況,停止後續AJAX動作

jQuery的$.ajax()自1.5版起改為回傳Promise物件(CommonJS提議的設計模式),並加入Deferred物件,提供done(), fail(), then(), reject(), resolve()等機制, 處理非同步作業如虎添翼,再加上能讓非同步作業依序執行的神奇方法--pipe(),要搞定循序執行AJAX呼叫應是小事一椿,但因為跟Deferred/Promise交情不夠,摸索一陣子才試出來。

為了模擬伺服器端回應,先寫一個SomeJob.ashx,接受?m=*參數,傳回m參數及現在時間,當m=E時則故意throw ApplicationException模擬程式出錯:

<%@ WebHandler Language="C#" Class="SomeJob" %>
 
using System;
using System.Web;
using System.Threading;
 
public class SomeJob : IHttpHandler {
    
    public void ProcessRequest (HttpContext context) {
        context.Response.ContentType = "text/plain";
        var mode = context.Request["m"];
        if (mode == "E")
            throw new ApplicationException("Error On Demand");
        Thread.Sleep(1000);
        context.Response.Write(
            string.Format("{0}-{1:HH:mm:ss.fff}",
                mode, DateTime.Now));
    }
 
    public bool IsReusable {
        get {
            return false;
        }
    }
 
}

Client端程式碼如下:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Ajax Pipeline Test</title>
    <script src="../Scripts/jquery-1.8.0.js"></script>
    <script>
        $(function () {
            var queue = ["S", "S", "F", "S"]; //["S", "S", "E", "S"];
            var chain = $.Deferred().resolve();
 
            $.each(queue, function (i, v) {
                //使用pipe()串接後取得新的Promise
                chain = chain.pipe(function () {
                    return $.post("SomeJob.ashx", { m: v })
                    .pipe(
                     //doneFilter, 形同ajax success事件,但要傳回Promise物件
                        function (res) {
                            $("#message").append("<li>" + res + "</li>");
                            //傳回reject的Deferred物件可中斷pipe()執行
                            if (res.substr(0, 1) == "F")
                                return $.Deferred().reject();
                            //傳回resolve的Deferred物件繼續pipe()
                            return $.Deferred().resolve();
                        }
                     //failFilter, 形同ajax error事件,但要傳回Promise物件
                        , function (jqXHR, textStatus, errorThrown) {
                            alert("ERROR-" + errorThrown);
                            return $.Deferred().reject();
   });
                });
            });
        });
    </script>
</head>
<body>
    <ul id="message"></ul>
</body>
</html>

在JavaScript端,先宣告一個$.Deferred()並執行resolve()傳回Promise物件--chain,之後用chain = chain.pipe()的方式逐一串接$.post()傳回Promise物件。由於需依結果做判斷,所以用$.post().pipe(doneFilter, failFilter)取代原本$.ajax()的success及error事件,而doneFilter、failFilter可傳回Promise決定後續作業是否繼續執行,這就完成了"以jQuery循序執行AJAX呼叫,並依結果決定是否繼續"的目標,收工!

【延伸閱讀】


Comments

# by 亞米斯

//doneFilter, 形同ajax success事件,但要傳回Promise物件 //failFilter, 形同ajax success事件,但要傳回Promise物件 兩個都是 ajax success 嗎??

# by Jeffrey

to 亞米斯,感謝你的細心指正,failFilter應該形同error,已修正本文!! (感覺上,那天要是貼出的文沒錯字,八成是我被盜帳號了! orz...)

# by SAM

請問我使用Jquery 的$.ajax 執行二個 ajax的function,第一個ajax回傳結果的時間較久,我想讓第一個ajax未回傳結果前,立即執行第二個ajax,是否可以做到呢?我試過用async=true沒有效,第二個ajax都會在第一個執行完後才會執行。 function AjaxTest() { Ajax1(); Ajax2(); } function Ajax1() { oAjaxData = $("#oClientForm").serialize(); $.ajax({ type: "POST", url: 'Ajax1.ashx', data: oAjaxData, dataType: "xml", async: true, error: function (xhr, ajaxOptions, thrownError) { }, success: function (oXml) { ... } }); } function Ajax2() { oAjaxData = $("#oClientForm").serialize(); $.ajax({ type: "POST", url: 'Ajax2.ashx', data: oAjaxData, dataType: "xml", async: true, error: function (xhr, ajaxOptions, thrownError) { }, success: function (oXml) { ... } }); }

# by Jeffrey

to SAM, 依我的理解,兩個$.ajax()發出的Request可以並行執行,你的狀況有沒有可能是ajax1.ashx、ajax2.ashx間因Session鎖定效應而讓ajax2被排在ajax1之後才執行? (參考: http://blog.darkthread.net/post-2011-08-27-aspx-session-lock.aspx "啟用Session的ASP.NET網頁,因鎖定限制有可能出現單一時間內只能有一個Request被處理的情況")

# by SAM

感謝黑大指點~果然是在主頁有寫入Session的關係,黑大真是經驗豐富,竟然能在程式碼不完整的情形下找到在下的問題,非常感謝您的回覆,不知是否只要有寫入Session,現單一時間內只能有一個Request被處理的情況就無法避免呢?

# by Jeffrey

to SAM, 依我所知,只要EnableSessionState="true"就會因WriterLock造成單一時間只有一個ASPX可以執行的結果,若某些網頁對Session只讀不寫,你可以用EnableSessionState="ReadOnly"調成ReaderLock,就能同時執行。(細節可參考前篇留言的文章)

# by SAM

了解了,感謝黑大,請允許在下將解答連結放至MSDN論壇, http://goo.gl/vSb3o

Post a comment


27 - 2 =