reqwest.js 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631
  1. /*!
  2. * Reqwest! A general purpose XHR connection manager
  3. * license MIT (c) Dustin Diaz 2015
  4. * https://github.com/ded/reqwest
  5. */
  6. !function (name, context, definition) {
  7. if (typeof module != 'undefined' && module.exports) module.exports = definition()
  8. else if (typeof define == 'function' && define.amd) define(definition)
  9. else context[name] = definition()
  10. }('reqwest', this, function () {
  11. var context = this
  12. if ('window' in context) {
  13. var doc = document
  14. , byTag = 'getElementsByTagName'
  15. , head = doc[byTag]('head')[0]
  16. } else {
  17. var XHR2
  18. try {
  19. XHR2 = require('xhr2')
  20. } catch (ex) {
  21. throw new Error('Peer dependency `xhr2` required! Please npm install xhr2')
  22. }
  23. }
  24. var httpsRe = /^http/
  25. , protocolRe = /(^\w+):\/\//
  26. , twoHundo = /^(20\d|1223)$/ //http://stackoverflow.com/questions/10046972/msie-returns-status-code-of-1223-for-ajax-request
  27. , readyState = 'readyState'
  28. , contentType = 'Content-Type'
  29. , requestedWith = 'X-Requested-With'
  30. , uniqid = 0
  31. , callbackPrefix = 'reqwest_' + (+new Date())
  32. , lastValue // data stored by the most recent JSONP callback
  33. , xmlHttpRequest = 'XMLHttpRequest'
  34. , xDomainRequest = 'XDomainRequest'
  35. , noop = function () {}
  36. , isArray = typeof Array.isArray == 'function'
  37. ? Array.isArray
  38. : function (a) {
  39. return a instanceof Array
  40. }
  41. , defaultHeaders = {
  42. 'contentType': 'application/x-www-form-urlencoded'
  43. , 'requestedWith': xmlHttpRequest
  44. , 'accept': {
  45. '*': 'text/javascript, text/html, application/xml, text/xml, */*'
  46. , 'xml': 'application/xml, text/xml'
  47. , 'html': 'text/html'
  48. , 'text': 'text/plain'
  49. , 'json': 'application/json, text/javascript'
  50. , 'js': 'application/javascript, text/javascript'
  51. }
  52. }
  53. , xhr = function(o) {
  54. // is it x-domain
  55. if (o['crossOrigin'] === true) {
  56. var xhr = context[xmlHttpRequest] ? new XMLHttpRequest() : null
  57. if (xhr && 'withCredentials' in xhr) {
  58. return xhr
  59. } else if (context[xDomainRequest]) {
  60. return new XDomainRequest()
  61. } else {
  62. throw new Error('Browser does not support cross-origin requests')
  63. }
  64. } else if (context[xmlHttpRequest]) {
  65. return new XMLHttpRequest()
  66. } else if (XHR2) {
  67. return new XHR2()
  68. } else {
  69. return new ActiveXObject('Microsoft.XMLHTTP')
  70. }
  71. }
  72. , globalSetupOptions = {
  73. dataFilter: function (data) {
  74. return data
  75. }
  76. }
  77. function succeed(r) {
  78. var protocol = protocolRe.exec(r.url)
  79. protocol = (protocol && protocol[1]) || context.location.protocol
  80. return httpsRe.test(protocol) ? twoHundo.test(r.request.status) : !!r.request.response
  81. }
  82. function handleReadyState(r, success, error) {
  83. return function () {
  84. // use _aborted to mitigate against IE err c00c023f
  85. // (can't read props on aborted request objects)
  86. if (r._aborted) return error(r.request)
  87. if (r._timedOut) return error(r.request, 'Request is aborted: timeout')
  88. if (r.request && r.request[readyState] == 4) {
  89. r.request.onreadystatechange = noop
  90. if (succeed(r)) success(r.request)
  91. else
  92. error(r.request)
  93. }
  94. }
  95. }
  96. function setHeaders(http, o) {
  97. var headers = o['headers'] || {}
  98. , h
  99. headers['Accept'] = headers['Accept']
  100. || defaultHeaders['accept'][o['type']]
  101. || defaultHeaders['accept']['*']
  102. var isAFormData = typeof FormData !== 'undefined' && (o['data'] instanceof FormData);
  103. // breaks cross-origin requests with legacy browsers
  104. if (!o['crossOrigin'] && !headers[requestedWith]) headers[requestedWith] = defaultHeaders['requestedWith']
  105. if (!headers[contentType] && !isAFormData) headers[contentType] = o['contentType'] || defaultHeaders['contentType']
  106. for (h in headers)
  107. headers.hasOwnProperty(h) && 'setRequestHeader' in http && http.setRequestHeader(h, headers[h])
  108. }
  109. function setCredentials(http, o) {
  110. if (typeof o['withCredentials'] !== 'undefined' && typeof http.withCredentials !== 'undefined') {
  111. http.withCredentials = !!o['withCredentials']
  112. }
  113. }
  114. function generalCallback(data) {
  115. lastValue = data
  116. }
  117. function urlappend (url, s) {
  118. return url + (/\?/.test(url) ? '&' : '?') + s
  119. }
  120. function handleJsonp(o, fn, err, url) {
  121. var reqId = uniqid++
  122. , cbkey = o['jsonpCallback'] || 'callback' // the 'callback' key
  123. , cbval = o['jsonpCallbackName'] || reqwest.getcallbackPrefix(reqId)
  124. , cbreg = new RegExp('((^|\\?|&)' + cbkey + ')=([^&]+)')
  125. , match = url.match(cbreg)
  126. , script = doc.createElement('script')
  127. , loaded = 0
  128. , isIE10 = navigator.userAgent.indexOf('MSIE 10.0') !== -1
  129. if (match) {
  130. if (match[3] === '?') {
  131. url = url.replace(cbreg, '$1=' + cbval) // wildcard callback func name
  132. } else {
  133. cbval = match[3] // provided callback func name
  134. }
  135. } else {
  136. url = urlappend(url, cbkey + '=' + cbval) // no callback details, add 'em
  137. }
  138. context[cbval] = generalCallback
  139. script.type = 'text/javascript'
  140. script.src = url
  141. script.async = true
  142. if (typeof script.onreadystatechange !== 'undefined' && !isIE10) {
  143. // need this for IE due to out-of-order onreadystatechange(), binding script
  144. // execution to an event listener gives us control over when the script
  145. // is executed. See http://jaubourg.net/2010/07/loading-script-as-onclick-handler-of.html
  146. script.htmlFor = script.id = '_reqwest_' + reqId
  147. }
  148. script.onload = script.onreadystatechange = function () {
  149. if ((script[readyState] && script[readyState] !== 'complete' && script[readyState] !== 'loaded') || loaded) {
  150. return false
  151. }
  152. script.onload = script.onreadystatechange = null
  153. script.onclick && script.onclick()
  154. // Call the user callback with the last value stored and clean up values and scripts.
  155. fn(lastValue)
  156. lastValue = undefined
  157. head.removeChild(script)
  158. loaded = 1
  159. }
  160. // Add the script to the DOM head
  161. head.appendChild(script)
  162. // Enable JSONP timeout
  163. return {
  164. abort: function () {
  165. script.onload = script.onreadystatechange = null
  166. err({}, 'Request is aborted: timeout', {})
  167. lastValue = undefined
  168. head.removeChild(script)
  169. loaded = 1
  170. }
  171. }
  172. }
  173. function getRequest(fn, err) {
  174. var o = this.o
  175. , method = (o['method'] || 'GET').toUpperCase()
  176. , url = typeof o === 'string' ? o : o['url']
  177. // convert non-string objects to query-string form unless o['processData'] is false
  178. , data = (o['processData'] !== false && o['data'] && typeof o['data'] !== 'string')
  179. ? reqwest.toQueryString(o['data'])
  180. : (o['data'] || null)
  181. , http
  182. , sendWait = false
  183. // if we're working on a GET request and we have data then we should append
  184. // query string to end of URL and not post data
  185. if ((o['type'] == 'jsonp' || method == 'GET') && data) {
  186. url = urlappend(url, data)
  187. data = null
  188. }
  189. if (o['type'] == 'jsonp') return handleJsonp(o, fn, err, url)
  190. // get the xhr from the factory if passed
  191. // if the factory returns null, fall-back to ours
  192. http = (o.xhr && o.xhr(o)) || xhr(o)
  193. http.open(method, url, o['async'] === false ? false : true)
  194. setHeaders(http, o)
  195. setCredentials(http, o)
  196. if (context[xDomainRequest] && http instanceof context[xDomainRequest]) {
  197. http.onload = fn
  198. http.onerror = err
  199. // NOTE: see
  200. // http://social.msdn.microsoft.com/Forums/en-US/iewebdevelopment/thread/30ef3add-767c-4436-b8a9-f1ca19b4812e
  201. http.onprogress = function() {}
  202. sendWait = true
  203. } else {
  204. http.onreadystatechange = handleReadyState(this, fn, err)
  205. }
  206. o['before'] && o['before'](http)
  207. if (sendWait) {
  208. setTimeout(function () {
  209. http.send(data)
  210. }, 200)
  211. } else {
  212. http.send(data)
  213. }
  214. return http
  215. }
  216. function Reqwest(o, fn) {
  217. this.o = o
  218. this.fn = fn
  219. init.apply(this, arguments)
  220. }
  221. function setType(header) {
  222. // json, javascript, text/plain, text/html, xml
  223. if (header === null) return undefined; //In case of no content-type.
  224. if (header.match('json')) return 'json'
  225. if (header.match('javascript')) return 'js'
  226. if (header.match('text')) return 'html'
  227. if (header.match('xml')) return 'xml'
  228. }
  229. function init(o, fn) {
  230. this.url = typeof o == 'string' ? o : o['url']
  231. this.timeout = null
  232. // whether request has been fulfilled for purpose
  233. // of tracking the Promises
  234. this._fulfilled = false
  235. // success handlers
  236. this._successHandler = function(){}
  237. this._fulfillmentHandlers = []
  238. // error handlers
  239. this._errorHandlers = []
  240. // complete (both success and fail) handlers
  241. this._completeHandlers = []
  242. this._erred = false
  243. this._responseArgs = {}
  244. var self = this
  245. fn = fn || function () {}
  246. if (o['timeout']) {
  247. this.timeout = setTimeout(function () {
  248. timedOut()
  249. }, o['timeout'])
  250. }
  251. if (o['success']) {
  252. this._successHandler = function () {
  253. o['success'].apply(o, arguments)
  254. }
  255. }
  256. if (o['error']) {
  257. this._errorHandlers.push(function () {
  258. o['error'].apply(o, arguments)
  259. })
  260. }
  261. if (o['complete']) {
  262. this._completeHandlers.push(function () {
  263. o['complete'].apply(o, arguments)
  264. })
  265. }
  266. function complete (resp) {
  267. o['timeout'] && clearTimeout(self.timeout)
  268. self.timeout = null
  269. while (self._completeHandlers.length > 0) {
  270. self._completeHandlers.shift()(resp)
  271. }
  272. }
  273. function success (resp) {
  274. var type = o['type'] || resp && setType(resp.getResponseHeader('Content-Type')) // resp can be undefined in IE
  275. resp = (type !== 'jsonp') ? self.request : resp
  276. // use global data filter on response text
  277. var filteredResponse = globalSetupOptions.dataFilter(resp.responseText, type)
  278. , r = filteredResponse
  279. try {
  280. resp.responseText = r
  281. } catch (e) {
  282. // can't assign this in IE<=8, just ignore
  283. }
  284. if (r) {
  285. switch (type) {
  286. case 'json':
  287. try {
  288. resp = context.JSON ? context.JSON.parse(r) : eval('(' + r + ')')
  289. } catch (err) {
  290. return error(resp, 'Could not parse JSON in response', err)
  291. }
  292. break
  293. case 'js':
  294. resp = eval(r)
  295. break
  296. case 'html':
  297. resp = r
  298. break
  299. case 'xml':
  300. resp = resp.responseXML
  301. && resp.responseXML.parseError // IE trololo
  302. && resp.responseXML.parseError.errorCode
  303. && resp.responseXML.parseError.reason
  304. ? null
  305. : resp.responseXML
  306. break
  307. }
  308. }
  309. self._responseArgs.resp = resp
  310. self._fulfilled = true
  311. fn(resp)
  312. self._successHandler(resp)
  313. while (self._fulfillmentHandlers.length > 0) {
  314. resp = self._fulfillmentHandlers.shift()(resp)
  315. }
  316. complete(resp)
  317. }
  318. function timedOut() {
  319. self._timedOut = true
  320. self.request.abort()
  321. }
  322. function error(resp, msg, t) {
  323. resp = self.request
  324. self._responseArgs.resp = resp
  325. self._responseArgs.msg = msg
  326. self._responseArgs.t = t
  327. self._erred = true
  328. while (self._errorHandlers.length > 0) {
  329. self._errorHandlers.shift()(resp, msg, t)
  330. }
  331. complete(resp)
  332. }
  333. this.request = getRequest.call(this, success, error)
  334. }
  335. Reqwest.prototype = {
  336. abort: function () {
  337. this._aborted = true
  338. this.request.abort()
  339. }
  340. , retry: function () {
  341. init.call(this, this.o, this.fn)
  342. }
  343. /**
  344. * Small deviation from the Promises A CommonJs specification
  345. * http://wiki.commonjs.org/wiki/Promises/A
  346. */
  347. /**
  348. * `then` will execute upon successful requests
  349. */
  350. , then: function (success, fail) {
  351. success = success || function () {}
  352. fail = fail || function () {}
  353. if (this._fulfilled) {
  354. this._responseArgs.resp = success(this._responseArgs.resp)
  355. } else if (this._erred) {
  356. fail(this._responseArgs.resp, this._responseArgs.msg, this._responseArgs.t)
  357. } else {
  358. this._fulfillmentHandlers.push(success)
  359. this._errorHandlers.push(fail)
  360. }
  361. return this
  362. }
  363. /**
  364. * `always` will execute whether the request succeeds or fails
  365. */
  366. , always: function (fn) {
  367. if (this._fulfilled || this._erred) {
  368. fn(this._responseArgs.resp)
  369. } else {
  370. this._completeHandlers.push(fn)
  371. }
  372. return this
  373. }
  374. /**
  375. * `fail` will execute when the request fails
  376. */
  377. , fail: function (fn) {
  378. if (this._erred) {
  379. fn(this._responseArgs.resp, this._responseArgs.msg, this._responseArgs.t)
  380. } else {
  381. this._errorHandlers.push(fn)
  382. }
  383. return this
  384. }
  385. , 'catch': function (fn) {
  386. return this.fail(fn)
  387. }
  388. }
  389. function reqwest(o, fn) {
  390. return new Reqwest(o, fn)
  391. }
  392. // normalize newline variants according to spec -> CRLF
  393. function normalize(s) {
  394. return s ? s.replace(/\r?\n/g, '\r\n') : ''
  395. }
  396. function serial(el, cb) {
  397. var n = el.name
  398. , t = el.tagName.toLowerCase()
  399. , optCb = function (o) {
  400. // IE gives value="" even where there is no value attribute
  401. // 'specified' ref: http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-862529273
  402. if (o && !o['disabled'])
  403. cb(n, normalize(o['attributes']['value'] && o['attributes']['value']['specified'] ? o['value'] : o['text']))
  404. }
  405. , ch, ra, val, i
  406. // don't serialize elements that are disabled or without a name
  407. if (el.disabled || !n) return
  408. switch (t) {
  409. case 'input':
  410. if (!/reset|button|image|file/i.test(el.type)) {
  411. ch = /checkbox/i.test(el.type)
  412. ra = /radio/i.test(el.type)
  413. val = el.value
  414. // WebKit gives us "" instead of "on" if a checkbox has no value, so correct it here
  415. ;(!(ch || ra) || el.checked) && cb(n, normalize(ch && val === '' ? 'on' : val))
  416. }
  417. break
  418. case 'textarea':
  419. cb(n, normalize(el.value))
  420. break
  421. case 'select':
  422. if (el.type.toLowerCase() === 'select-one') {
  423. optCb(el.selectedIndex >= 0 ? el.options[el.selectedIndex] : null)
  424. } else {
  425. for (i = 0; el.length && i < el.length; i++) {
  426. el.options[i].selected && optCb(el.options[i])
  427. }
  428. }
  429. break
  430. }
  431. }
  432. // collect up all form elements found from the passed argument elements all
  433. // the way down to child elements; pass a '<form>' or form fields.
  434. // called with 'this'=callback to use for serial() on each element
  435. function eachFormElement() {
  436. var cb = this
  437. , e, i
  438. , serializeSubtags = function (e, tags) {
  439. var i, j, fa
  440. for (i = 0; i < tags.length; i++) {
  441. fa = e[byTag](tags[i])
  442. for (j = 0; j < fa.length; j++) serial(fa[j], cb)
  443. }
  444. }
  445. for (i = 0; i < arguments.length; i++) {
  446. e = arguments[i]
  447. if (/input|select|textarea/i.test(e.tagName)) serial(e, cb)
  448. serializeSubtags(e, [ 'input', 'select', 'textarea' ])
  449. }
  450. }
  451. // standard query string style serialization
  452. function serializeQueryString() {
  453. return reqwest.toQueryString(reqwest.serializeArray.apply(null, arguments))
  454. }
  455. // { 'name': 'value', ... } style serialization
  456. function serializeHash() {
  457. var hash = {}
  458. eachFormElement.apply(function (name, value) {
  459. if (name in hash) {
  460. hash[name] && !isArray(hash[name]) && (hash[name] = [hash[name]])
  461. hash[name].push(value)
  462. } else hash[name] = value
  463. }, arguments)
  464. return hash
  465. }
  466. // [ { name: 'name', value: 'value' }, ... ] style serialization
  467. reqwest.serializeArray = function () {
  468. var arr = []
  469. eachFormElement.apply(function (name, value) {
  470. arr.push({name: name, value: value})
  471. }, arguments)
  472. return arr
  473. }
  474. reqwest.serialize = function () {
  475. if (arguments.length === 0) return ''
  476. var opt, fn
  477. , args = Array.prototype.slice.call(arguments, 0)
  478. opt = args.pop()
  479. opt && opt.nodeType && args.push(opt) && (opt = null)
  480. opt && (opt = opt.type)
  481. if (opt == 'map') fn = serializeHash
  482. else if (opt == 'array') fn = reqwest.serializeArray
  483. else fn = serializeQueryString
  484. return fn.apply(null, args)
  485. }
  486. reqwest.toQueryString = function (o, trad) {
  487. var prefix, i
  488. , traditional = trad || false
  489. , s = []
  490. , enc = encodeURIComponent
  491. , add = function (key, value) {
  492. // If value is a function, invoke it and return its value
  493. value = ('function' === typeof value) ? value() : (value == null ? '' : value)
  494. s[s.length] = enc(key) + '=' + enc(value)
  495. }
  496. // If an array was passed in, assume that it is an array of form elements.
  497. if (isArray(o)) {
  498. for (i = 0; o && i < o.length; i++) add(o[i]['name'], o[i]['value'])
  499. } else {
  500. // If traditional, encode the "old" way (the way 1.3.2 or older
  501. // did it), otherwise encode params recursively.
  502. for (prefix in o) {
  503. if (o.hasOwnProperty(prefix)) buildParams(prefix, o[prefix], traditional, add)
  504. }
  505. }
  506. // spaces should be + according to spec
  507. return s.join('&').replace(/%20/g, '+')
  508. }
  509. function buildParams(prefix, obj, traditional, add) {
  510. var name, i, v
  511. , rbracket = /\[\]$/
  512. if (isArray(obj)) {
  513. // Serialize array item.
  514. for (i = 0; obj && i < obj.length; i++) {
  515. v = obj[i]
  516. if (traditional || rbracket.test(prefix)) {
  517. // Treat each array item as a scalar.
  518. add(prefix, v)
  519. } else {
  520. buildParams(prefix + '[' + (typeof v === 'object' ? i : '') + ']', v, traditional, add)
  521. }
  522. }
  523. } else if (obj && obj.toString() === '[object Object]') {
  524. // Serialize object item.
  525. for (name in obj) {
  526. buildParams(prefix + '[' + name + ']', obj[name], traditional, add)
  527. }
  528. } else {
  529. // Serialize scalar item.
  530. add(prefix, obj)
  531. }
  532. }
  533. reqwest.getcallbackPrefix = function () {
  534. return callbackPrefix
  535. }
  536. // jQuery and Zepto compatibility, differences can be remapped here so you can call
  537. // .ajax.compat(options, callback)
  538. reqwest.compat = function (o, fn) {
  539. if (o) {
  540. o['type'] && (o['method'] = o['type']) && delete o['type']
  541. o['dataType'] && (o['type'] = o['dataType'])
  542. o['jsonpCallback'] && (o['jsonpCallbackName'] = o['jsonpCallback']) && delete o['jsonpCallback']
  543. o['jsonp'] && (o['jsonpCallback'] = o['jsonp'])
  544. }
  545. return new Reqwest(o, fn)
  546. }
  547. reqwest.ajaxSetup = function (options) {
  548. options = options || {}
  549. for (var k in options) {
  550. globalSetupOptions[k] = options[k]
  551. }
  552. }
  553. return reqwest
  554. });