CODE-以jQuery循序執行AJAX呼叫,並依結果決定是否繼續

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

用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呼叫,並依結果決定是否繼續"的目標,收工!

【延伸閱讀】

歡迎推文分享:
Published 05 September 2012 03:53 PM 由 Jeffrey
Filed under: ,
Views: 23,763



意見

# 亞米斯 said on 06 September, 2012 04:08 AM

//doneFilter, 形同ajax success事件,但要傳回Promise物件

//failFilter, 形同ajax success事件,但要傳回Promise物件

兩個都是 ajax success 嗎??

# Jeffrey said on 06 September, 2012 05:27 AM

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

# SAM said on 29 November, 2012 03:04 AM

請問我使用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) {

     ...

     }

   });

 }

# Jeffrey said on 29 November, 2012 07:29 AM

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

# SAM said on 29 November, 2012 10:09 AM

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

# Jeffrey said on 29 November, 2012 05:43 PM

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

# SAM said on 29 November, 2012 08:04 PM

了解了,感謝黑大,請允許在下將解答連結放至MSDN論壇,

http://goo.gl/vSb3o

你的看法呢?

(必要的) 
(必要的) 
(選擇性的)
(必要的) 
(提醒: 因快取機制,您的留言幾分鐘後才會顯示在網站,請耐心稍候)

5 + 3 =

搜尋

Go

<September 2012>
SunMonTueWedThuFriSat
2627282930311
2345678
9101112131415
16171819202122
23242526272829
30123456
 
RSS
創用 CC 授權條款
【廣告】
twMVC
最新回應

Tags 分類檢視
關於作者

一個醉心技術又酷愛分享的Coding魔人,十年的IT職場生涯,寫過系統、管過專案, 也帶過團隊,最後還是無怨無悔地選擇了技術鑽研這條路,近年來則以做一個"有為的中年人"自許。

文章典藏
其他功能

這個部落格


Syndication