jQuery源码阅读(十四)---aJax 模块与异步队列联系

关于ajaxjQuery做了封装,并且考虑了很多浏览器兼容问题,以及跨域问题。当然,这种异步操作离不开我们之前分析的延迟对象。所以这一次,主要是对ajax模块中对于延迟对象的应用进行分析。

$.ajax函数

先来分析$.ajax()函数。
在这里,我必须贴一张图来展示下ajax源码的整体框架。此图引用于高云(jQuery技术内幕第13章)
这里写图片描述

上图清楚得描述了整个ajax过程中的一系列流程,同时是按照源码顺序列出来的。根据上图,我们也能大体还原出来一个ajax源码框架。

通常,我们调ajax时,比较常见的就是 $.ajax({ url: 'XX.php', success: function(){ } })这种形式, 但也有$.ajax('XX.php').done(function(){ }).fail(function(){ })这种形式。所以,不难想象,$.ajax()返回的也是一个延迟对象。即上图上面最后返回的jqXHR对象。而这个jqXHR对象又类似于我们原生XMLHttpRequest对象,需要有自己的一些方法和属性,因此,可以利用延迟对象的promise(arguments)方法来扩展延迟对象。


function( url, options)
{
    //创建延迟对象
    var deferred = $.Deferred();
    //创建一个回调对象,用于最终完成时,触发完成回调函数。
    var completeDeferred = $.Callbacks('once memory')

    jqXHR = {
        //拥有一些XMLHttpRequest的属性和方法,比如readyState、 setRequestHeader等等。
        readyState: 0,
        setRequestHeader: function(){
        }   
    }

    deferred.promise(jqXHR);
    //此时jqXHR是一个延迟对象,并且拥有自己特有的属性和方法
    //将我们常用的success、error、complete这些回调方法分别与延迟对象的回调列表对应起来。
    jqXHR.success = jqXHR.done;
    jqXHR.error = jqXHR.fail;
    jqXHR.complete = completeDeferred.add;

    return jqXHR;
}

上面这部分框架,只是创建jqXHR对象,并将其与延迟对象的各个列表联系起来,并最终返回这个延迟对象的过程。那么,问题来了,到底随着请求的产生,发送及响应,是如何触发回调函数的?

我们再回过头看上面的图,最后当transport.send( requestHeaders, done );执行之后,这个函数主要执行了xhr.send();这个函数,然后监听readyStateChange事件,由xhr.responseText或者xhr.responseXML判断,有响应数据时,便会触发send中的回调函数done
因为transport.send针对了script和 其他数据类型两种情况,我们这里只讨论后一种情况。其transport.send中的部分源码如下:

//创建XMLHttpRequest对象
//打开socket连接:xhr.open()
if ( s.username ) {
    xhr.open( s.type, s.url, s.async, s.username, s.password );
} else {    
    xhr.open( s.type, s.url, s.async );
}
//设置HTTP请求头信息

//接着是发送请求:xhr.send(),如果是post方法,有参数
xhr.send( ( s.hasContent && s.data ) || null );

//下面的思想,跟我们用原生XMLHttpRequest对象实现aJax是一样的。也是通过监听readyState是否变化的事件从而触发回调函数

//如果是同步ajax请求或者状态已经完成,那么手动执行回调函数
if ( !s.async || xhr.readyState === 4 ) {
    callback();
} else {
    //如果是异步,并且状态还未变成已完成,那么通过监听readystatechange事件来触发回调函数
    handle = ++xhrId;
    if ( xhrOnUnloadAbort ) {
        // 这里主要是在IE浏览器中
        if ( !xhrCallbacks ) {
            xhrCallbacks = {};
            //因为在IE浏览器中,关闭浏览器时,仍然保持着连接,所以需要手动将所有的ajax请求回调函数取消。
            jQuery( window ).unload( xhrOnUnloadAbort );
        }
        xhrCallbacks[ handle ] = callback;
    }
    //不是IE浏览器时,监听readystatechange事件
    xhr.onreadystatechange = callback;
}

那么回调函数callback 是干嘛的?

//isAbort表示是否取消请求
function( _, isAbort ) {
    var status,
    statusText,
    responseHeaders,
    responses,
    xml;

    // Firefox throws exceptions when accessing properties
    // of an xhr when a network error occured
    // http://helpful.knobsdials.com/index.php/Component_returned_failure_code:_0x80040111_(NS_ERROR_NOT_AVAILABLE)
    try {

        // Was never called and is aborted or complete
        if ( callback && ( isAbort || xhr.readyState === 4 ) ) {

            // Only called once
            callback = undefined;

            // Do not keep as active anymore
            if ( handle ) {
                xhr.onreadystatechange = jQuery.noop;
                if ( xhrOnUnloadAbort ) {
                    delete xhrCallbacks[ handle ];
                }
            }

            // If it's an abort
                if ( isAbort ) {
                    // Abort it manually if needed
                    if ( xhr.readyState !== 4 ) {
                        xhr.abort();
                    }
                } else {
                    status = xhr.status;
                    responseHeaders = xhr.getAllResponseHeaders();
                    responses = {};
                    xml = xhr.responseXML;

                    // Construct response list
                    if ( xml && xml.documentElement /* #4958 */ ) {
                        responses.xml = xml;
                    }

                    // When requesting binary data, IE6-9 will throw an exception
                    // on any attempt to access responseText (#11426)
                    try {
                        responses.text = xhr.responseText;
                    } catch( _ ) {
                    }

                    // Firefox throws an exception when accessing
                    // statusText for faulty cross-domain requests
                    try {
                        statusText = xhr.statusText;
                    } catch( e ) {
                        // We normalize with Webkit giving an empty statusText
                        statusText = "";
                    }

                    // Filter status for non standard behaviors

                    // If the request is local and we have data: assume a success
                    // (success with no data won't get notified, that's the best we
                    // can do given current implementations)
                    if ( !status && s.isLocal && !s.crossDomain ) {
                        status = responses.text ? 200 : 404;
                        // IE - #1450: sometimes returns 1223 when it should be 204
                 } else if ( status === 1223 ) {
                        status = 204;
                }
            }
        }
    } catch( firefoxAccessException ) {
        if ( !isAbort ) {
            complete( -1, firefoxAccessException );
        }
    }

    //如果得到响应数据,触发complete函数,这个函数是外部传过来的函数,也就是我们外部的done回调函数
    if ( responses ) {
        complete( status, statusText, responses, responseHeaders );
    }
}

done函数中,主要根据status来判断响应是否成功,如果成功,触发jqXHR的成功回调列表中的回调;如果失败,则触发jqXHR的失败回调列表中的回调,除了这些处理,还会触发complete回调函数,同时会根据情况触发各种ajax全局函数。

function done( status, nativeStatusText, responses, headers ) {

    //如果state为2,说明这个回调已经执行过了,不会再执行第二次了
    if ( state === 2 ) {
        return;
    }


    state = 2;

    // Clear timeout if it exists
    if ( timeoutTimer ) {
        clearTimeout( timeoutTimer );
    }

    // Dereference transport for early garbage collection
    // (no matter how long the jqXHR object will be used)
    transport = undefined;

    // Cache response headers
    responseHeadersString = headers || "";

    // Set readyState
    jqXHR.readyState = status > 0 ? 4 : 0;

    var isSuccess,
        success,
        error,
        statusText = nativeStatusText,
        response = responses ? ajaxHandleResponses( s, jqXHR, responses ) : undefined,
        lastModified,
        etag;

    // 下面这部分代码主要是根据status状态,来判断是否请求成功。
    //请求成功时,isSuccess标志就被设为true
    if ( status >= 200 && status < 300 || status === 304 ) {

        // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
        if ( s.ifModified ) {

            if ( ( lastModified = jqXHR.getResponseHeader( "Last-Modified" ) ) ) {
                jQuery.lastModified[ ifModifiedKey ] = lastModified;
            }
            if ( ( etag = jqXHR.getResponseHeader( "Etag" ) ) ) {
                jQuery.etag[ ifModifiedKey ] = etag;
            }
        }

        // If not modified
        if ( status === 304 ) {

            statusText = "notmodified";
            isSuccess = true;

        //If we have data
        } else {

            try {
                success = ajaxConvert( s, response );
                statusText = "success";
                isSuccess = true;
            } catch(e) {
                // We have a parsererror
                statusText = "parsererror";
                error = e;
            }
        }
    } else {
        // We extract error from statusText
        // then normalize statusText and status for non-aborts
        error = statusText;
        if ( !statusText || status ) {
            statusText = "error";
            if ( status < 0 ) {
                status = 0;
            }
        }
    }

    // Set data for the fake xhr object
    jqXHR.status = status;
    jqXHR.statusText = "" + ( nativeStatusText || statusText );

    //由上面得到的请求成功与否的状态isSuccess
    //请求成功,触发deferred.resolveWith(),使该延迟对象处于resolve状态,执行成功回调列表里的回调函数
    //请求失败,触发deferred.rejectWith(),使该延迟对象处于reject状态,执行失败回调列表里的回调函数
    if ( isSuccess ) {
        deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
    } else {
        deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
    }

    //Status-dependent callbacks
    jqXHR.statusCode( statusCode );
    statusCode = undefined;

    if ( fireGlobals ) {
        //触发ajax全局事件,ajaxSuccess或者 ajaxError事件
        globalEventContext.trigger( "ajax" + ( isSuccess ? "Success" : "Error" ),
                [ jqXHR, s, isSuccess ? success : error ] );
    }

    //执行completeDeferred回调对象的fireWith()方法,执行回调列表中的回调函数
    completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );

    if ( fireGlobals ) {
        globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );
        // Handle the global AJAX counter
        if ( !( --jQuery.active ) ) {
            jQuery.event.trigger( "ajaxStop" );
        }
    }
}

上面done函数主要是通过触发延迟对象的resolveWithrejectWith方法来执行我们添加的success: function(){ }error: function(){ }方法,并且通过触发completeDeferred回调对象的fireWith()方法来执行我们添加的complete: function(){ }方法,那么我们在参数中添加的方法是如何加到回调列表中去的?

jQuery中是这么做的:

for ( i in { success: 1, error: 1, complete: 1 } ) {
    jqXHR[ i ]( s[ i ] );
}

通过对{ success: 1, error: 1, complete: 1 }遍历,分别执行jqXHR.success, jqXHR.error, jqXHR.complete方法。这三个方法在前面也讲过了,分别对应到deferred对象和回调对象的done, failadd方法。所以这个操作就是将s.success, s.error, s.complete添加到对应回调列表中的。那么s.success, s.error, s.complete分别是什么呢?
其中

//option是我们传入到ajax中的配置选项对象
s = jQuery.ajaxSetup( {}, options )
//s就是 我们传的配置选项与jQuery源码中默认的配置的组合,里面有我们传入的url,没传url的话默认就是自身的href(这是在jQuery源码默认的配置选项中设置的); 还有我们传入的success回调函数, error回调函数以及complete函数。因此,上面说的s.success, s.error和s.complete其实就是我们传入的回调函数。

这样在done函数中,触发resolveWith或者rejectWith,就会执行对应列表中的回调了,也就是我们传的回调函数。complete也是同样的道理。

这样,就实现了ajax在请求的过程中,根据响应的成功与否,触发不同的回调列表中的函数,以此来实现异步请求的目的。这个过程很复杂,且比较绕,并且期间处理了很多很多情况,比如设置头部,处理跨域,这些都还没深入,只是从异步队列角度来分析,如果就实现了异步,旨在对异步队列应用有更深的理解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值