1 /**
  2  * @fileOverview encapsulation of io object . as transaction object in yui3
  3  * @author yiminghe@gmail.com
  4  */
  5 KISSY.add("ajax/XhrObject", function (S, undefined) {
  6 
  7     var OK_CODE = 200,
  8         Promise = S.Promise,
  9         MULTIPLE_CHOICES = 300,
 10         NOT_MODIFIED = 304,
 11     // get individual response header from responseheader str
 12         rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/mg;
 13 
 14     function handleResponseData(xhrObject) {
 15 
 16         // text xml 是否原生转化支持
 17         var text = xhrObject.responseText,
 18             xml = xhrObject.responseXML,
 19             c = xhrObject.config,
 20             cConverts = c.converters,
 21             xConverts = xhrObject.converters || {},
 22             type,
 23             responseData,
 24             contents = c.contents,
 25             dataType = c.dataType;
 26 
 27         // 例如 script 直接是js引擎执行,没有返回值,不需要自己处理初始返回值
 28         // jsonp 时还需要把 script 转换成 json,后面还得自己来
 29         if (text || xml) {
 30 
 31             var contentType = xhrObject.mimeType || xhrObject.getResponseHeader("Content-Type");
 32 
 33             // 去除无用的通用格式
 34             while (dataType[0] == "*") {
 35                 dataType.shift();
 36             }
 37 
 38             if (!dataType.length) {
 39                 // 获取源数据格式,放在第一个
 40                 for (type in contents) {
 41                     if (contents[type].test(contentType)) {
 42                         if (dataType[0] != type) {
 43                             dataType.unshift(type);
 44                         }
 45                         break;
 46                     }
 47                 }
 48             }
 49             // 服务器端没有告知(并且客户端没有 mimetype )默认 text 类型
 50             dataType[0] = dataType[0] || "text";
 51 
 52             //获得合适的初始数据
 53             if (dataType[0] == "text" && text !== undefined) {
 54                 responseData = text;
 55             }
 56             // 有 xml 值才直接取,否则可能还要从 xml 转
 57             else if (dataType[0] == "xml" && xml !== undefined) {
 58                 responseData = xml;
 59             } else {
 60                 var rawData = {text:text, xml:xml};
 61                 // 看能否从 text xml 转换到合适数据,并设置起始类型为 text/xml
 62                 S.each(["text", "xml"], function (prevType) {
 63                     var type = dataType[0],
 64                         converter = xConverts[prevType] && xConverts[prevType][type] ||
 65                             cConverts[prevType] && cConverts[prevType][type];
 66                     if (converter && rawData[prevType]) {
 67                         dataType.unshift(prevType);
 68                         responseData = prevType == "text" ? text : xml;
 69                         return false;
 70                     }
 71                 });
 72             }
 73         }
 74         var prevType = dataType[0];
 75 
 76         // 按照转化链把初始数据转换成我们想要的数据类型
 77         for (var i = 1; i < dataType.length; i++) {
 78             type = dataType[i];
 79 
 80             var converter = xConverts[prevType] && xConverts[prevType][type] ||
 81                 cConverts[prevType] && cConverts[prevType][type];
 82 
 83             if (!converter) {
 84                 throw "no covert for " + prevType + " => " + type;
 85             }
 86             responseData = converter(responseData);
 87 
 88             prevType = type;
 89         }
 90 
 91         xhrObject.responseData = responseData;
 92     }
 93 
 94     /**
 95      * @class A class for constructing io request instances. !Do Not New By Yourself!
 96      * @extends KISSY.Promise
 97      * @memberOf IO
 98      */
 99     function XhrObject(c) {
100         Promise.call(this);
101         S.mix(this, {
102             // 结构化数据,如 json
103             responseData:null,
104             config:c || {},
105             timeoutTimer:null,
106 
107             /**
108              * @field
109              * @memberOf IO.XhrObject#
110              * @description String typed data returned from server
111              */
112             responseText:null,
113             /**
114              * @field
115              * @memberOf IO.XhrObject#
116              * @description xml typed data returned from server
117              */
118             responseXML:null,
119             responseHeadersString:"",
120             responseHeaders:null,
121             requestHeaders:{},
122             /**
123              * @field
124              * @memberOf IO.XhrObject#
125              * @description <br>
126              * readyState of current request<br>
127              * 0: initialized<br>
128              * 1: send <br>
129              * 4: completed<br>
130              */
131             readyState:0,
132             state:0,
133             /**
134              * @field
135              * @memberOf IO.XhrObject#
136              * @description HTTP statusText of current request
137              */
138             statusText:null,
139             /**
140              * @field
141              * @memberOf IO.XhrObject#
142              * @description <br> HTTP Status Code of current request <br>
143              * eg:<br>
144              * 200 : ok<br>
145              * 404 : Not Found<br>
146              * 500 : Server Error<br>
147              */
148             status:0,
149             transport:null,
150             _defer:new S.Defer(this)
151         });
152     }
153 
154     S.extend(XhrObject, Promise,
155         /**
156          * @lends IO.XhrObject.prototype
157          */
158         {
159             // Caches the header
160             setRequestHeader:function (name, value) {
161                 var self = this;
162                 self.requestHeaders[ name ] = value;
163                 return self;
164             },
165 
166             /**
167              * get all response headers as string after request is completed
168              * @returns {String}
169              */
170             getAllResponseHeaders:function () {
171                 var self = this;
172                 return self.state === 2 ? self.responseHeadersString : null;
173             },
174 
175             /**
176              * get header value in response to specified header name
177              * @param {String} name header name
178              * @return {String} header value
179              */
180             getResponseHeader:function (name) {
181                 var match, self = this;
182                 if (self.state === 2) {
183                     if (!self.responseHeaders) {
184                         self.responseHeaders = {};
185                         while (( match = rheaders.exec(self.responseHeadersString) )) {
186                             self.responseHeaders[ match[1] ] = match[ 2 ];
187                         }
188                     }
189                     match = self.responseHeaders[ name ];
190                 }
191                 return match === undefined ? null : match;
192             },
193 
194             // Overrides response content-type header
195             overrideMimeType:function (type) {
196                 var self = this;
197                 if (!self.state) {
198                     self.mimeType = type;
199                 }
200                 return self;
201             },
202 
203             /**
204              * cancel this request
205              * @param {String} [statusText=abort] error reason as current request object's statusText
206              */
207             abort:function (statusText) {
208                 var self = this;
209                 statusText = statusText || "abort";
210                 if (self.transport) {
211                     self.transport.abort(statusText);
212                 }
213                 self._xhrReady(0, statusText);
214                 return self;
215             },
216 
217             /**
218              * get native XMLHttpRequest
219              * @since 1.3
220              */
221             getNativeXhr:function () {
222                 var transport;
223                 if (transport = this.transport) {
224                     return transport.nativeXhr;
225                 }
226             },
227 
228             _xhrReady:function (status, statusText) {
229                 var self = this;
230                 // 只能执行一次,防止重复执行
231                 // 例如完成后,调用 abort
232 
233                 // 到这要么成功,调用success
234                 // 要么失败,调用 error
235                 // 最终都会调用 complete
236                 if (self.state == 2) {
237                     return;
238                 }
239                 self.state = 2;
240                 self.readyState = 4;
241                 var isSuccess;
242                 if (status >= OK_CODE && status < MULTIPLE_CHOICES || status == NOT_MODIFIED) {
243 
244                     if (status == NOT_MODIFIED) {
245                         statusText = "notmodified";
246                         isSuccess = true;
247                     } else {
248                         try {
249                             handleResponseData(self);
250                             statusText = "success";
251                             isSuccess = true;
252                         } catch (e) {
253                             statusText = "parsererror : " + e;
254                         }
255                     }
256 
257                 } else {
258                     if (status < 0) {
259                         status = 0;
260                     }
261                 }
262 
263                 self.status = status;
264                 self.statusText = statusText;
265 
266                 var defer = self._defer;
267                 defer[isSuccess ? "resolve" : "reject"]([self.responseData, self.statusText, self]);
268             }
269         }
270     );
271 
272     return XhrObject;
273 });