├── .gitignore ├── .jshintrc ├── .senv ├── .travis.yml ├── HISTORY.md ├── LICENSE ├── MAINTAINING.md ├── Makefile ├── README.md ├── bower.json ├── examples └── index.html ├── fetch.js ├── package.json ├── script ├── phantomjs ├── saucelabs ├── saucelabs-result ├── saucelabs-start ├── saucelabs-status ├── server └── test ├── src └── assert.js └── test ├── .gitignore ├── .jshintrc ├── test-worker.html ├── test.html ├── test.js └── worker.js /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | bower_components/ 3 | node_modules/ 4 | test/assert.js 5 | test/es5shimsham.js 6 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "curly": true, 3 | "eqeqeq": true, 4 | "es3": true, 5 | "immed": true, 6 | "indent": 2, 7 | "latedef": true, 8 | "newcap": true, 9 | "noarg": true, 10 | "quotmark": true, 11 | "undef": true, 12 | "unused": true, 13 | "strict": true, 14 | "trailing": true, 15 | "asi": true, 16 | "boss": true, 17 | "esnext": true, 18 | "eqnull": true, 19 | "browser": true, 20 | "worker": true 21 | } 22 | -------------------------------------------------------------------------------- /.senv: -------------------------------------------------------------------------------- 1 | hqhBWY7QnNkyaM/nUOnpYMUWhjMyWw8bl6EJOPSxHlgopMEfoIOestV3mhVEwEKTpTnHtx9pv+zl62lYr6kBrVfSaDg7uF+WEMXlMo/HecmE3Qz21JLkBDZYc4JAPvjF9dObU1WSVAHI4uFsVcCrylzulR4DnV3zWum4l+TvJ0rcXBCISeNPLj/YQxyJMDm+QSSDY6Sg1LXNHWUU6ORLKrW4XrIZmL4WxEIm7IXThK7gNslojDlz7krRJ8XIS5nCx/kI7L7ZfJwd3VG+d56kW7a97U1OJcjNXVL1L3OwX+oqKAu4ojOehkTBepmnOtu62LRHd1Jgr07faT++hvL5sA== 2 | SWg+ILWFcdwg0rU+8fYUoZSjxd1MWmRBJGS5EpbFvrD0Lzdv2lGmrfTMfDGjV0nmpnZhSkAXcWJENw7J9XRQAMmbGq93NL8K0Tv1B49xhuIffIkJlmRP3fosIJGrpHBZWa8v/87Jtirqgg58xisjh7LZRrhrHiw3BPeZumv2t7YO1YzAGfcxthcdtxPs+jBl8iufvhO03/oE8Gd7d3FjfnOBDJwyoTcYTNvTXdh85GINJRKhYhSdq1pq+ASDNxIvfEBUhLzwvQsG4ScuDcUpC7T63dfH+BhtakT2EkC/8w5vRw9jK7DM2UevOSnLZroC05CQDawz453X6CxgLlwJJg== 3 | ZaPgL6eMy+Gg1ti+ZYH09tXH+pUA0oEaPMb5vTsxUye0Hqam2zRtJ5AYlZQO1lTp0IuJjFTTuxve07tfy0MPgP9Bntq9YkB06+kRMVJPRB1kSAoq/tTGv1binbe4g8bN9B7LFu1/ODPyNVPEOaisy4WjRBym8t5n59cQFl4keQqqoB+fWUf4DVXN+7Xi84wy1gSanwH3jKCVtOhBzaWIKY+pLk2qYYLUUuKlB5SVk9ywEzFOoStVlZXGemOVVlXrtOg3wW83cce5zAmdbrrQz+nAquPuZepDmVvmg1mDdQ30BQ3p6BJixwyvsBRtdVQAJEr98nRMpnkyylaQNKAlcw== 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | before_script: make 3 | script: make test 4 | env: 5 | global: 6 | - SAUCE_USERNAME=github-fetch 7 | - SAUCE_ACCESS_KEY=c3d37f93-0c2e-4834-9da5-eddc0d8c6299 8 | matrix: 9 | - PHANTOMJS=1 10 | - SAUCE_PLATFORM="Windows 7" SAUCE_BROWSER="googlechrome" SAUCE_VERSION="" 11 | - SAUCE_PLATFORM="Windows 7" SAUCE_BROWSER="internet explorer" SAUCE_VERSION="11" 12 | - SAUCE_PLATFORM="Windows 7" SAUCE_BROWSER="internet explorer" SAUCE_VERSION="10" 13 | - SAUCE_PLATFORM="Windows 7" SAUCE_BROWSER="internet explorer" SAUCE_VERSION="9" 14 | addons: 15 | sauce_connect: true 16 | deploy: 17 | provider: npm 18 | email: matt@mattandre.ws 19 | api_key: 20 | secure: FcQZz0HCJhrz8FZyyfkuXj4cwoBP+PeQ7LzOVU1bG7v3kX7Z33n8glD+32QScT2Uu369exdjkk3mzCaCMsfZTTvRm9STnuJIrPtdB2/FwfaWiyJiB1oZ2UCd5UQM0zMiQrtg+gR8FUBBgi3GICOkzAqTbso+C7P2IJtvpP9RTTI= 21 | on: 22 | all_branches: true 23 | tags: true 24 | repo: github/fetch 25 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | 1.5.0 / 2017-02-28 2 | ================== 3 | * Fix self 4 | 5 | 1.4.3 / 2016-06-22 6 | ================== 7 | * Compatibility when defining the global object, merged #5 8 | 9 | 1.4.2 / 2016-03-13 10 | ================== 11 | * Fix request and response content-type different bug 12 | 13 | 1.4.1 / 2016-01-20 14 | ================== 15 | * Make blob supporting different encoding, fixed #1 16 | 17 | 1.4.0 / 2015-12-09 18 | ================== 19 | * Sync with github/fetch 20 | * Fix promise resolve twice bug 21 | 22 | 1.3.1 / 2015-09-08 23 | ================== 24 | * Use console.warn to log messages 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 GitHub, Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /MAINTAINING.md: -------------------------------------------------------------------------------- 1 | # Maintaining 2 | 3 | ## Releasing a new version 4 | 5 | This project follows [semver](http://semver.org/). So if you are making a bug 6 | fix, only increment the patch level "1.0.x". If any new files are added, a 7 | minor version "1.x.x" bump is in order. 8 | 9 | ### Make a release commit 10 | 11 | To prepare the release commit: 12 | 13 | 1. Edit the [bower.json](https://github.com/github/fetch/blob/master/bower.json) 14 | `version` value. 15 | 2. Change the npm [package.json](https://github.com/github/fetch/blob/master/package.json) 16 | `version` value to match. 17 | 3. Make a single commit with the description as "Fetch 1.x.x". 18 | 4. Finally, tag the commit with `v1.x.x`. 19 | 20 | ``` 21 | $ git pull 22 | $ vim bower.json 23 | $ vim package.json 24 | $ git add bower.json package.json 25 | $ git commit -m "Fetch 1.x.x" 26 | $ git tag v1.x.x 27 | $ git push 28 | $ git push --tags 29 | ``` 30 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | build: node_modules/ assert es5shimsham 2 | 3 | es5shimsham: 4 | (curl https://cdnjs.cloudflare.com/ajax/libs/es5-shim/4.1.1/es5-shim.min.js; echo ''; curl https://cdnjs.cloudflare.com/ajax/libs/es5-shim/4.1.1/es5-sham.min.js) > test/es5shimsham.js 5 | 6 | test: node_modules/ build lint 7 | ./script/test 8 | 9 | lint: node_modules/ 10 | ./node_modules/.bin/jshint *.js test/*.js 11 | 12 | assert: node_modules/ 13 | ./node_modules/.bin/browserify src/assert.js -s assert > test/assert.js 14 | 15 | node_modules/: 16 | npm install 17 | 18 | clean: 19 | rm -rf ./node_modules 20 | 21 | .PHONY: build clean lint test saucelabs travis 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # window.fetch polyfill 2 | 3 | **This fork supports IE8 with es5-shim, es5-sham and es6-promise.** 4 | 5 | **If you also use JSONP, checkout [fetch-jsonp](https://github.com/camsong/fetch-jsonp).** 6 | 7 | **Fetch API is still very new and not fully supported in some browsers, so you may 8 | need to check browser verson as well as if `window.fetch` exists. In this case, 9 | you can set `window.__disableNativeFetch = true` to use AJAX polyfill always.** 10 | 11 | The global `fetch` function is an easier way to make web requests and handle 12 | responses than using an XMLHttpRequest. This polyfill is written as closely as 13 | possible to the standard Fetch specification at https://fetch.spec.whatwg.org. 14 | 15 | ## Installation 16 | 17 | ```sh 18 | $ npm install fetch-ie8 --save 19 | ``` 20 | 21 | You'll also need a Promise polyfill for older browsers. 22 | 23 | ```sh 24 | $ npm install es6-promise 25 | ``` 26 | 27 | Run this to polyfill the global environment at the beginning of your application. 28 | ```js 29 | require('es6-promise').polyfill(); 30 | ``` 31 | 32 | (For a node.js implementation, try [node-fetch](https://github.com/bitinn/node-fetch)) 33 | 34 | ## Usage 35 | 36 | The `fetch` function supports any HTTP method. We'll focus on GET and POST 37 | example requests. 38 | 39 | ### HTML 40 | 41 | ```javascript 42 | fetch('/users.html') 43 | .then(function(response) { 44 | return response.text() 45 | }).then(function(body) { 46 | document.body.innerHTML = body 47 | }) 48 | ``` 49 | 50 | ### JSON 51 | 52 | ```javascript 53 | fetch('/users.json') 54 | .then(function(response) { 55 | return response.json() 56 | }).then(function(json) { 57 | console.log('parsed json', json) 58 | }).catch(function(ex) { 59 | console.log('parsing failed', ex) 60 | }) 61 | ``` 62 | 63 | ### Response metadata 64 | 65 | ```javascript 66 | fetch('/users.json').then(function(response) { 67 | console.log(response.headers.get('Content-Type')) 68 | console.log(response.headers.get('Date')) 69 | console.log(response.status) 70 | console.log(response.statusText) 71 | }) 72 | ``` 73 | 74 | ### Post form 75 | 76 | ```javascript 77 | var form = document.querySelector('form') 78 | 79 | fetch('/query', { 80 | method: 'post', 81 | body: new FormData(form) 82 | }) 83 | ``` 84 | 85 | ### Post JSON 86 | 87 | ```javascript 88 | fetch('/users', { 89 | method: 'post', 90 | headers: { 91 | 'Accept': 'application/json', 92 | 'Content-Type': 'application/json' 93 | }, 94 | body: JSON.stringify({ 95 | name: 'Hubot', 96 | login: 'hubot', 97 | }) 98 | }) 99 | ``` 100 | 101 | ### File upload 102 | 103 | ```javascript 104 | var input = document.querySelector('input[type="file"]') 105 | 106 | var form = new FormData() 107 | form.append('file', input.files[0]) 108 | form.append('user', 'hubot') 109 | 110 | fetch('/avatars', { 111 | method: 'post', 112 | body: form 113 | }) 114 | ``` 115 | 116 | ### Success and error handlers 117 | 118 | This causes `fetch` to behave like jQuery's `$.ajax` by rejecting the `Promise` 119 | on HTTP failure status codes like 404, 500, etc. The response `Promise` is 120 | resolved only on successful, 200 level, status codes. 121 | 122 | ```javascript 123 | function status(response) { 124 | if (response.status >= 200 && response.status < 300) { 125 | return response 126 | } 127 | throw new Error(response.statusText) 128 | } 129 | 130 | function json(response) { 131 | return response.json() 132 | } 133 | 134 | fetch('/users') 135 | .then(status) 136 | .then(json) 137 | .then(function(json) { 138 | console.log('request succeeded with json response', json) 139 | }).catch(function(error) { 140 | console.log('request failed', error) 141 | }) 142 | ``` 143 | 144 | ### Response URL caveat 145 | 146 | The `Response` object has a URL attribute for the final responded resource. 147 | Usually this is the same as the `Request` url, but in the case of a redirect, 148 | its all transparent. Newer versions of XHR include a `responseURL` attribute 149 | that returns this value. But not every browser supports this. The compromise 150 | requires setting a special server side header to tell the browser what URL it 151 | just requested (yeah, I know browsers). 152 | 153 | ``` ruby 154 | response.headers['X-Request-URL'] = request.url 155 | ``` 156 | 157 | If you want `response.url` to be reliable, you'll want to set this header. The 158 | day that you ditch this polyfill and use native fetch only, you can remove the 159 | header hack. 160 | 161 | ## Browser Support 162 | 163 |  |  |  |  |  164 | --- | --- | --- | --- | --- | 165 | Latest ✔ | Latest ✔ | 8+ ✔ | Latest ✔ | 6.1+ ✔ | 166 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fetch-ie8", 3 | "version": "1.3.1", 4 | "main": "fetch.js", 5 | "devDependencies": { 6 | "es6-promise": "2.1.0" 7 | }, 8 | "ignore": [ 9 | ".*", 10 | "*.md", 11 | "examples/", 12 | "Makefile", 13 | "package.json", 14 | "script/", 15 | "test/" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /fetch.js: -------------------------------------------------------------------------------- 1 | (function(self) { 2 | 'use strict'; 3 | 4 | // if __disableNativeFetch is set to true, the it will always polyfill fetch 5 | // with Ajax. 6 | if (!self.__disableNativeFetch && self.fetch) { 7 | return 8 | } 9 | 10 | function normalizeName(name) { 11 | if (typeof name !== 'string') { 12 | name = String(name) 13 | } 14 | if (/[^a-z0-9\-#$%&'*+.\^_`|~]/i.test(name)) { 15 | throw new TypeError('Invalid character in header field name') 16 | } 17 | return name.toLowerCase() 18 | } 19 | 20 | function normalizeValue(value) { 21 | if (typeof value !== 'string') { 22 | value = String(value) 23 | } 24 | return value 25 | } 26 | 27 | function Headers(headers) { 28 | this.map = {} 29 | 30 | if (headers instanceof Headers) { 31 | headers.forEach(function(value, name) { 32 | this.append(name, value) 33 | }, this) 34 | 35 | } else if (headers) { 36 | Object.getOwnPropertyNames(headers).forEach(function(name) { 37 | this.append(name, headers[name]) 38 | }, this) 39 | } 40 | } 41 | 42 | Headers.prototype.append = function(name, value) { 43 | name = normalizeName(name) 44 | value = normalizeValue(value) 45 | var list = this.map[name] 46 | if (!list) { 47 | list = [] 48 | this.map[name] = list 49 | } 50 | list.push(value) 51 | } 52 | 53 | Headers.prototype['delete'] = function(name) { 54 | delete this.map[normalizeName(name)] 55 | } 56 | 57 | Headers.prototype.get = function(name) { 58 | var values = this.map[normalizeName(name)] 59 | return values ? values[0] : null 60 | } 61 | 62 | Headers.prototype.getAll = function(name) { 63 | return this.map[normalizeName(name)] || [] 64 | } 65 | 66 | Headers.prototype.has = function(name) { 67 | return this.map.hasOwnProperty(normalizeName(name)) 68 | } 69 | 70 | Headers.prototype.set = function(name, value) { 71 | this.map[normalizeName(name)] = [normalizeValue(value)] 72 | } 73 | 74 | Headers.prototype.forEach = function(callback, thisArg) { 75 | Object.getOwnPropertyNames(this.map).forEach(function(name) { 76 | this.map[name].forEach(function(value) { 77 | callback.call(thisArg, value, name, this) 78 | }, this) 79 | }, this) 80 | } 81 | 82 | function consumed(body) { 83 | if (body.bodyUsed) { 84 | return Promise.reject(new TypeError('Already read')) 85 | } 86 | body.bodyUsed = true 87 | } 88 | 89 | function fileReaderReady(reader) { 90 | return new Promise(function(resolve, reject) { 91 | reader.onload = function() { 92 | resolve(reader.result) 93 | } 94 | reader.onerror = function() { 95 | reject(reader.error) 96 | } 97 | }) 98 | } 99 | 100 | function readBlobAsArrayBuffer(blob) { 101 | var reader = new FileReader() 102 | reader.readAsArrayBuffer(blob) 103 | return fileReaderReady(reader) 104 | } 105 | 106 | function readBlobAsText(blob, options) { 107 | var reader = new FileReader() 108 | var contentType = options.headers.map['content-type'] ? options.headers.map['content-type'].toString() : '' 109 | var regex = /charset\=[0-9a-zA-Z\-\_]*;?/ 110 | var _charset = blob.type.match(regex) || contentType.match(regex) 111 | var args = [blob] 112 | 113 | if(_charset) { 114 | args.push(_charset[0].replace(/^charset\=/, '').replace(/;$/, '')) 115 | } 116 | 117 | reader.readAsText.apply(reader, args) 118 | return fileReaderReady(reader) 119 | } 120 | 121 | var support = { 122 | blob: 'FileReader' in self && 'Blob' in self && (function() { 123 | try { 124 | new Blob(); 125 | return true 126 | } catch(e) { 127 | return false 128 | } 129 | })(), 130 | formData: 'FormData' in self, 131 | arrayBuffer: 'ArrayBuffer' in self 132 | } 133 | 134 | function Body() { 135 | this.bodyUsed = false 136 | 137 | 138 | this._initBody = function(body, options) { 139 | this._bodyInit = body 140 | if (typeof body === 'string') { 141 | this._bodyText = body 142 | } else if (support.blob && Blob.prototype.isPrototypeOf(body)) { 143 | this._bodyBlob = body 144 | this._options = options 145 | } else if (support.formData && FormData.prototype.isPrototypeOf(body)) { 146 | this._bodyFormData = body 147 | } else if (!body) { 148 | this._bodyText = '' 149 | } else if (support.arrayBuffer && ArrayBuffer.prototype.isPrototypeOf(body)) { 150 | // Only support ArrayBuffers for POST method. 151 | // Receiving ArrayBuffers happens via Blobs, instead. 152 | } else { 153 | throw new Error('unsupported BodyInit type') 154 | } 155 | } 156 | 157 | if (support.blob) { 158 | this.blob = function() { 159 | var rejected = consumed(this) 160 | if (rejected) { 161 | return rejected 162 | } 163 | 164 | if (this._bodyBlob) { 165 | return Promise.resolve(this._bodyBlob) 166 | } else if (this._bodyFormData) { 167 | throw new Error('could not read FormData body as blob') 168 | } else { 169 | return Promise.resolve(new Blob([this._bodyText])) 170 | } 171 | } 172 | 173 | this.arrayBuffer = function() { 174 | return this.blob().then(readBlobAsArrayBuffer) 175 | } 176 | 177 | this.text = function() { 178 | var rejected = consumed(this) 179 | if (rejected) { 180 | return rejected 181 | } 182 | 183 | if (this._bodyBlob) { 184 | return readBlobAsText(this._bodyBlob, this._options) 185 | } else if (this._bodyFormData) { 186 | throw new Error('could not read FormData body as text') 187 | } else { 188 | return Promise.resolve(this._bodyText) 189 | } 190 | } 191 | } else { 192 | this.text = function() { 193 | var rejected = consumed(this) 194 | return rejected ? rejected : Promise.resolve(this._bodyText) 195 | } 196 | } 197 | 198 | if (support.formData) { 199 | this.formData = function() { 200 | return this.text().then(decode) 201 | } 202 | } 203 | 204 | this.json = function() { 205 | return this.text().then(JSON.parse) 206 | } 207 | 208 | return this 209 | } 210 | 211 | // HTTP methods whose capitalization should be normalized 212 | var methods = ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST', 'PUT'] 213 | 214 | function normalizeMethod(method) { 215 | var upcased = method.toUpperCase() 216 | return (methods.indexOf(upcased) > -1) ? upcased : method 217 | } 218 | 219 | function Request(input, options) { 220 | options = options || {} 221 | var body = options.body 222 | if (Request.prototype.isPrototypeOf(input)) { 223 | if (input.bodyUsed) { 224 | throw new TypeError('Already read') 225 | } 226 | this.url = input.url 227 | this.credentials = input.credentials 228 | if (!options.headers) { 229 | this.headers = new Headers(input.headers) 230 | } 231 | this.method = input.method 232 | this.mode = input.mode 233 | if (!body) { 234 | body = input._bodyInit 235 | input.bodyUsed = true 236 | } 237 | } else { 238 | this.url = input 239 | } 240 | 241 | this.credentials = options.credentials || this.credentials || 'omit' 242 | if (options.headers || !this.headers) { 243 | this.headers = new Headers(options.headers) 244 | } 245 | this.method = normalizeMethod(options.method || this.method || 'GET') 246 | this.mode = options.mode || this.mode || null 247 | this.referrer = null 248 | 249 | if ((this.method === 'GET' || this.method === 'HEAD') && body) { 250 | throw new TypeError('Body not allowed for GET or HEAD requests') 251 | } 252 | this._initBody(body, options) 253 | } 254 | 255 | Request.prototype.clone = function() { 256 | return new Request(this) 257 | } 258 | 259 | function decode(body) { 260 | var form = new FormData() 261 | body.trim().split('&').forEach(function(bytes) { 262 | if (bytes) { 263 | var split = bytes.split('=') 264 | var name = split.shift().replace(/\+/g, ' ') 265 | var value = split.join('=').replace(/\+/g, ' ') 266 | form.append(decodeURIComponent(name), decodeURIComponent(value)) 267 | } 268 | }) 269 | return form 270 | } 271 | 272 | function headers(xhr) { 273 | var head = new Headers() 274 | var pairs = xhr.getAllResponseHeaders().trim().split('\n') 275 | pairs.forEach(function(header) { 276 | var split = header.trim().split(':') 277 | var key = split.shift().trim() 278 | var value = split.join(':').trim() 279 | head.append(key, value) 280 | }) 281 | return head 282 | } 283 | 284 | Body.call(Request.prototype) 285 | 286 | function Response(bodyInit, options) { 287 | if (!options) { 288 | options = {} 289 | } 290 | 291 | this._initBody(bodyInit, options) 292 | this.type = 'default' 293 | this.status = options.status 294 | this.ok = this.status >= 200 && this.status < 300 295 | this.statusText = options.statusText 296 | this.headers = options.headers instanceof Headers ? options.headers : new Headers(options.headers) 297 | this.url = options.url || '' 298 | } 299 | 300 | Body.call(Response.prototype) 301 | 302 | Response.prototype.clone = function() { 303 | return new Response(this._bodyInit, { 304 | status: this.status, 305 | statusText: this.statusText, 306 | headers: new Headers(this.headers), 307 | url: this.url 308 | }) 309 | } 310 | 311 | Response.error = function() { 312 | var response = new Response(null, {status: 0, statusText: ''}) 313 | response.type = 'error' 314 | return response 315 | } 316 | 317 | var redirectStatuses = [301, 302, 303, 307, 308] 318 | 319 | Response.redirect = function(url, status) { 320 | if (redirectStatuses.indexOf(status) === -1) { 321 | throw new RangeError('Invalid status code') 322 | } 323 | 324 | return new Response(null, {status: status, headers: {location: url}}) 325 | } 326 | 327 | self.Headers = Headers; 328 | self.Request = Request; 329 | self.Response = Response; 330 | 331 | self.fetch = function(input, init) { 332 | return new Promise(function(resolve, reject) { 333 | var request 334 | if (Request.prototype.isPrototypeOf(input) && !init) { 335 | request = input 336 | } else { 337 | request = new Request(input, init) 338 | } 339 | 340 | var xhr = new XMLHttpRequest() 341 | 342 | function responseURL() { 343 | if ('responseURL' in xhr) { 344 | return xhr.responseURL 345 | } 346 | 347 | // Avoid security warnings on getResponseHeader when not allowed by CORS 348 | if (/^X-Request-URL:/m.test(xhr.getAllResponseHeaders())) { 349 | return xhr.getResponseHeader('X-Request-URL') 350 | } 351 | 352 | return; 353 | } 354 | 355 | var __onLoadHandled = false; 356 | 357 | function onload() { 358 | if (xhr.readyState !== 4) { 359 | return 360 | } 361 | var status = (xhr.status === 1223) ? 204 : xhr.status 362 | if (status < 100 || status > 599) { 363 | if (__onLoadHandled) { return; } else { __onLoadHandled = true; } 364 | reject(new TypeError('Network request failed')) 365 | return 366 | } 367 | var options = { 368 | status: status, 369 | statusText: xhr.statusText, 370 | headers: headers(xhr), 371 | url: responseURL() 372 | } 373 | var body = 'response' in xhr ? xhr.response : xhr.responseText; 374 | 375 | if (__onLoadHandled) { return; } else { __onLoadHandled = true; } 376 | resolve(new Response(body, options)) 377 | } 378 | xhr.onreadystatechange = onload; 379 | xhr.onload = onload; 380 | xhr.onerror = function() { 381 | if (__onLoadHandled) { return; } else { __onLoadHandled = true; } 382 | reject(new TypeError('Network request failed')) 383 | } 384 | 385 | xhr.open(request.method, request.url, true) 386 | 387 | // `withCredentials` should be setted after calling `.open` in IE10 388 | // http://stackoverflow.com/a/19667959/1219343 389 | try { 390 | if (request.credentials === 'include') { 391 | if ('withCredentials' in xhr) { 392 | xhr.withCredentials = true; 393 | } else { 394 | console && console.warn && console.warn('withCredentials is not supported, you can ignore this warning'); 395 | } 396 | } 397 | } catch (e) { 398 | console && console.warn && console.warn('set withCredentials error:' + e); 399 | } 400 | 401 | if ('responseType' in xhr && support.blob) { 402 | xhr.responseType = 'blob' 403 | } 404 | 405 | request.headers.forEach(function(value, name) { 406 | xhr.setRequestHeader(name, value) 407 | }) 408 | 409 | xhr.send(typeof request._bodyInit === 'undefined' ? null : request._bodyInit) 410 | }) 411 | } 412 | self.fetch.polyfill = true 413 | 414 | // Support CommonJS 415 | if (typeof module !== 'undefined' && module.exports) { 416 | module.exports = self.fetch; 417 | } 418 | })(typeof self !== 'undefined' ? self : this); 419 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fetch-ie8", 3 | "version": "1.5.0", 4 | "main": "fetch.js", 5 | "repository": "camsong/fetch", 6 | "license": "MIT", 7 | "devDependencies": { 8 | "browserify": "^9.0.8", 9 | "es6-promise": "^2.1.1", 10 | "isarray": "0.0.1", 11 | "jshint": "2.5.2", 12 | "mocha": "2.1.0" 13 | }, 14 | "files": [ 15 | "LICENSE", 16 | "fetch.js" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /script/phantomjs: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | port=3900 6 | 7 | # Find next available port 8 | while lsof -i :$((++port)) >/dev/null; do true; done 9 | 10 | # Spin a test server in the background 11 | node ./script/server $port &>/dev/null & 12 | server_pid=$! 13 | trap "kill $server_pid" INT EXIT 14 | 15 | node ./node_modules/.bin/mocha-phantomjs -s localToRemoteUrlAccessEnabled=true -s webSecurityEnabled=false "http://localhost:$port/test/test.html" 16 | node ./node_modules/.bin/mocha-phantomjs -s localToRemoteUrlAccessEnabled=true -s webSecurityEnabled=false "http://localhost:$port/test/test-worker.html" 17 | -------------------------------------------------------------------------------- /script/saucelabs: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | port=8080 6 | 7 | # Spin a test server in the background 8 | node ./script/server $port &>/dev/null & 9 | server_pid=$! 10 | trap "kill $server_pid" INT EXIT 11 | 12 | job=$(./script/saucelabs-start "http://localhost:$port/test/test.html") 13 | 14 | while true 15 | do 16 | result=$(echo "$job" | ./script/saucelabs-status) 17 | [[ $result == *"\"completed\": true"* ]] && break 18 | sleep 1 19 | echo -n "." 20 | done 21 | 22 | echo -n "" 23 | 24 | echo "$result" | ./script/saucelabs-result 25 | -------------------------------------------------------------------------------- /script/saucelabs-result: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'json' 4 | 5 | obj = JSON.parse(ARGF.read) 6 | 7 | test = obj['js tests'][0] 8 | 9 | warn test['url'] 10 | warn test['platform'] 11 | warn test['result'].inspect 12 | 13 | if test['result'] && (test['result']['passes'] + test['result']['pending']) == test['result']['tests'] 14 | exit 0 15 | else 16 | exit 1 17 | end 18 | -------------------------------------------------------------------------------- /script/saucelabs-start: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | url="https://saucelabs.com/rest/v1/$SAUCE_USERNAME/js-tests" 6 | auth="$SAUCE_USERNAME:$SAUCE_ACCESS_KEY" 7 | header="Content-Type: application/json" 8 | 9 | data=$(cat <