├── README.md └── fetch.js /README.md: -------------------------------------------------------------------------------- 1 | ## react-native-fetch 2 | fetch plugin for react-native. (It is based the react-native official code, just change some codes) 3 | 4 | ### How to use? 5 | just replace file in `node_modules/react-native/Libraries/Fetch/fetch.js`, and reload your react-native app. 6 | 7 | ### Feature 8 | Get more error tips on the `network level`. 9 | For example: 10 | - `Could not connect to the server.` 11 | 12 | 13 | ## react-native-fetch 中文介绍 14 | react-native的网络插件(基于react-native官方的fetch的代码修改) 15 | 16 | ### 如何使用 17 | 用`fetch.js`替换项目中的`node_modules/react-native/Libraries/Fetch/fetch.js`即可,替换后需重新加载react-native应用。 18 | 19 | ### 特点 20 | 新增一些处于网络层的错误提示。 21 | 如下: 22 | - `Cloud not connect to the server.` 23 | 24 | -------------------------------------------------------------------------------- /fetch.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | * This is a third-party polyfill grabbed from: 10 | * https://github.com/github/fetch 11 | * 12 | * @providesModule fetch 13 | * @nolint 14 | */ 15 | /* eslint-disable */ 16 | 'use strict'; 17 | 18 | var self = {}; 19 | 20 | /** 21 | * Copyright (c) 2014 GitHub, Inc. 22 | * 23 | * Permission is hereby granted, free of charge, to any person obtaining 24 | * a copy of this software and associated documentation files (the 25 | * "Software"), to deal in the Software without restriction, including 26 | * without limitation the rights to use, copy, modify, merge, publish, 27 | * distribute, sublicense, and/or sell copies of the Software, and to 28 | * permit persons to whom the Software is furnished to do so, subject to 29 | * the following conditions: 30 | * 31 | * The above copyright notice and this permission notice shall be 32 | * included in all copies or substantial portions of the Software. 33 | * 34 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 35 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 36 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 37 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 38 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 39 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 40 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 41 | * 42 | * @preserve-header 43 | */ 44 | (function() { 45 | 'use strict'; 46 | 47 | if (self.fetch) { 48 | return 49 | } 50 | 51 | function normalizeName(name) { 52 | if (typeof name !== 'string') { 53 | name = String(name) 54 | } 55 | if (/[^a-z0-9\-#$%&'*+.\^_`|~]/i.test(name)) { 56 | throw new TypeError('Invalid character in header field name') 57 | } 58 | return name.toLowerCase() 59 | } 60 | 61 | function normalizeValue(value) { 62 | if (typeof value !== 'string') { 63 | value = String(value) 64 | } 65 | return value 66 | } 67 | 68 | function Headers(headers) { 69 | this.map = {} 70 | 71 | if (headers instanceof Headers) { 72 | headers.forEach(function(value, name) { 73 | this.append(name, value) 74 | }, this) 75 | 76 | } else if (headers) { 77 | Object.getOwnPropertyNames(headers).forEach(function(name) { 78 | this.append(name, headers[name]) 79 | }, this) 80 | } 81 | } 82 | 83 | Headers.prototype.append = function(name, value) { 84 | name = normalizeName(name) 85 | value = normalizeValue(value) 86 | var list = this.map[name] 87 | if (!list) { 88 | list = [] 89 | this.map[name] = list 90 | } 91 | list.push(value) 92 | } 93 | 94 | Headers.prototype['delete'] = function(name) { 95 | delete this.map[normalizeName(name)] 96 | } 97 | 98 | Headers.prototype.get = function(name) { 99 | var values = this.map[normalizeName(name)] 100 | return values ? values[0] : null 101 | } 102 | 103 | Headers.prototype.getAll = function(name) { 104 | return this.map[normalizeName(name)] || [] 105 | } 106 | 107 | Headers.prototype.has = function(name) { 108 | return this.map.hasOwnProperty(normalizeName(name)) 109 | } 110 | 111 | Headers.prototype.set = function(name, value) { 112 | this.map[normalizeName(name)] = [normalizeValue(value)] 113 | } 114 | 115 | Headers.prototype.forEach = function(callback, thisArg) { 116 | Object.getOwnPropertyNames(this.map).forEach(function(name) { 117 | this.map[name].forEach(function(value) { 118 | callback.call(thisArg, value, name, this) 119 | }, this) 120 | }, this) 121 | } 122 | 123 | function consumed(body) { 124 | if (body.bodyUsed) { 125 | return Promise.reject(new TypeError('Already read')) 126 | } 127 | body.bodyUsed = true 128 | } 129 | 130 | function fileReaderReady(reader) { 131 | return new Promise(function(resolve, reject) { 132 | reader.onload = function() { 133 | resolve(reader.result) 134 | } 135 | reader.onerror = function() { 136 | reject(reader.error) 137 | } 138 | }) 139 | } 140 | 141 | function readBlobAsArrayBuffer(blob) { 142 | var reader = new FileReader() 143 | reader.readAsArrayBuffer(blob) 144 | return fileReaderReady(reader) 145 | } 146 | 147 | function readBlobAsText(blob) { 148 | var reader = new FileReader() 149 | reader.readAsText(blob) 150 | return fileReaderReady(reader) 151 | } 152 | 153 | var support = { 154 | blob: typeof FileReader === 'function' && typeof Blob === 'function' && (function() { 155 | try { 156 | new Blob(); 157 | return true 158 | } catch(e) { 159 | return false 160 | } 161 | })(), 162 | formData: typeof FormData === 'function', 163 | arrayBuffer: typeof ArrayBuffer === 'function' 164 | } 165 | 166 | function Body() { 167 | this.bodyUsed = false 168 | 169 | this._initBody = function(body) { 170 | this._bodyInit = body 171 | if (typeof body === 'string') { 172 | this._bodyText = body 173 | } else if (support.blob && Blob.prototype.isPrototypeOf(body)) { 174 | this._bodyBlob = body 175 | } else if (support.formData && FormData.prototype.isPrototypeOf(body)) { 176 | this._bodyFormData = body 177 | } else if (!body) { 178 | this._bodyText = '' 179 | } else if (support.arrayBuffer && ArrayBuffer.prototype.isPrototypeOf(body)) { 180 | // Only support ArrayBuffers for POST method. 181 | // Receiving ArrayBuffers happens via Blobs, instead. 182 | } else { 183 | throw new Error('unsupported BodyInit type') 184 | } 185 | 186 | if (!this.headers.get('content-type')) { 187 | if (typeof body === 'string') { 188 | this.headers.set('content-type', 'text/plain;charset=UTF-8') 189 | } else if (this._bodyBlob && this._bodyBlob.type) { 190 | this.headers.set('content-type', this._bodyBlob.type) 191 | } 192 | } 193 | } 194 | 195 | if (support.blob) { 196 | this.blob = function() { 197 | var rejected = consumed(this) 198 | if (rejected) { 199 | return rejected 200 | } 201 | 202 | if (this._bodyBlob) { 203 | return Promise.resolve(this._bodyBlob) 204 | } else if (this._bodyFormData) { 205 | throw new Error('could not read FormData body as blob') 206 | } else { 207 | return Promise.resolve(new Blob([this._bodyText])) 208 | } 209 | } 210 | 211 | this.arrayBuffer = function() { 212 | return this.blob().then(readBlobAsArrayBuffer) 213 | } 214 | 215 | this.text = function() { 216 | var rejected = consumed(this) 217 | if (rejected) { 218 | return rejected 219 | } 220 | 221 | if (this._bodyBlob) { 222 | return readBlobAsText(this._bodyBlob) 223 | } else if (this._bodyFormData) { 224 | throw new Error('could not read FormData body as text') 225 | } else { 226 | return Promise.resolve(this._bodyText) 227 | } 228 | } 229 | } else { 230 | this.text = function() { 231 | var rejected = consumed(this) 232 | return rejected ? rejected : Promise.resolve(this._bodyText) 233 | } 234 | } 235 | 236 | if (support.formData) { 237 | this.formData = function() { 238 | return this.text().then(decode) 239 | } 240 | } 241 | 242 | this.json = function() { 243 | return this.text().then(JSON.parse) 244 | } 245 | 246 | return this 247 | } 248 | 249 | // HTTP methods whose capitalization should be normalized 250 | var methods = ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST', 'PUT'] 251 | 252 | function normalizeMethod(method) { 253 | var upcased = method.toUpperCase() 254 | return (methods.indexOf(upcased) > -1) ? upcased : method 255 | } 256 | 257 | function Request(input, options) { 258 | options = options || {} 259 | var body = options.body 260 | if (Request.prototype.isPrototypeOf(input)) { 261 | if (input.bodyUsed) { 262 | throw new TypeError('Already read') 263 | } 264 | this.url = input.url 265 | this.credentials = input.credentials 266 | if (!options.headers) { 267 | this.headers = new Headers(input.headers) 268 | } 269 | this.method = input.method 270 | this.mode = input.mode 271 | if (!body) { 272 | body = input._bodyInit 273 | input.bodyUsed = true 274 | } 275 | } else { 276 | this.url = input 277 | } 278 | 279 | this.credentials = options.credentials || this.credentials || 'omit' 280 | if (options.headers || !this.headers) { 281 | this.headers = new Headers(options.headers) 282 | } 283 | this.method = normalizeMethod(options.method || this.method || 'GET') 284 | this.mode = options.mode || this.mode || null 285 | this.referrer = null 286 | 287 | if ((this.method === 'GET' || this.method === 'HEAD') && body) { 288 | throw new TypeError('Body not allowed for GET or HEAD requests') 289 | } 290 | this._initBody(body) 291 | } 292 | 293 | Request.prototype.clone = function() { 294 | return new Request(this) 295 | } 296 | 297 | function decode(body) { 298 | var form = new FormData() 299 | body.trim().split('&').forEach(function(bytes) { 300 | if (bytes) { 301 | var split = bytes.split('=') 302 | var name = split.shift().replace(/\+/g, ' ') 303 | var value = split.join('=').replace(/\+/g, ' ') 304 | form.append(decodeURIComponent(name), decodeURIComponent(value)) 305 | } 306 | }) 307 | return form 308 | } 309 | 310 | function headers(xhr) { 311 | var head = new Headers() 312 | var pairs = xhr.getAllResponseHeaders().trim().split('\n') 313 | pairs.forEach(function(header) { 314 | var split = header.trim().split(':') 315 | var key = split.shift().trim() 316 | var value = split.join(':').trim() 317 | head.append(key, value) 318 | }) 319 | return head 320 | } 321 | 322 | Body.call(Request.prototype) 323 | 324 | function Response(bodyInit, options) { 325 | if (!options) { 326 | options = {} 327 | } 328 | 329 | this.type = 'default' 330 | this.status = options.status 331 | this.ok = this.status >= 200 && this.status < 300 332 | this.statusText = options.statusText 333 | this.headers = options.headers instanceof Headers ? options.headers : new Headers(options.headers) 334 | this.url = options.url || '' 335 | this._initBody(bodyInit) 336 | } 337 | Body.call(Response.prototype) 338 | 339 | Response.prototype.clone = function() { 340 | return new Response(this._bodyInit, { 341 | status: this.status, 342 | statusText: this.statusText, 343 | headers: new Headers(this.headers), 344 | url: this.url 345 | }) 346 | } 347 | 348 | Response.error = function() { 349 | var response = new Response(null, {status: 0, statusText: ''}) 350 | response.type = 'error' 351 | return response 352 | } 353 | 354 | var redirectStatuses = [301, 302, 303, 307, 308] 355 | 356 | Response.redirect = function(url, status) { 357 | if (redirectStatuses.indexOf(status) === -1) { 358 | throw new RangeError('Invalid status code') 359 | } 360 | 361 | return new Response(null, {status: status, headers: {location: url}}) 362 | } 363 | 364 | self.Headers = Headers; 365 | self.Request = Request; 366 | self.Response = Response; 367 | 368 | self.fetch = function(input, init) { 369 | return new Promise(function(resolve, reject) { 370 | var request 371 | if (Request.prototype.isPrototypeOf(input) && !init) { 372 | request = input 373 | } else { 374 | request = new Request(input, init) 375 | } 376 | 377 | var xhr = new XMLHttpRequest() 378 | 379 | function responseURL() { 380 | if ('responseURL' in xhr) { 381 | return xhr.responseURL 382 | } 383 | 384 | // Avoid security warnings on getResponseHeader when not allowed by CORS 385 | if (/^X-Request-URL:/m.test(xhr.getAllResponseHeaders())) { 386 | return xhr.getResponseHeader('X-Request-URL') 387 | } 388 | 389 | return; 390 | } 391 | 392 | xhr.onload = function() { 393 | var status = (xhr.status === 1223) ? 204 : xhr.status 394 | if (status < 100 || status > 599) { 395 | reject(new TypeError('Network request failed')) 396 | return 397 | } 398 | 399 | var options = { 400 | status: status, 401 | statusText: xhr.statusText, 402 | headers: headers(xhr), 403 | url: responseURL() 404 | } 405 | var body = 'response' in xhr ? xhr.response : xhr.responseText; 406 | resolve(new Response(body, options)) 407 | } 408 | 409 | xhr.onerror = function(e) { 410 | if(e && e.target && e.target._responseText){ 411 | return reject(new Error(e.target._responseText)); 412 | } 413 | reject(new TypeError('Network request failed')) 414 | } 415 | 416 | xhr.open(request.method, request.url, true) 417 | 418 | if (request.credentials === 'include') { 419 | xhr.withCredentials = true 420 | } 421 | 422 | if ('responseType' in xhr && support.blob) { 423 | xhr.responseType = 'blob' 424 | } 425 | 426 | request.headers.forEach(function(value, name) { 427 | xhr.setRequestHeader(name, value) 428 | }) 429 | 430 | xhr.send(typeof request._bodyInit === 'undefined' ? null : request._bodyInit) 431 | }) 432 | } 433 | self.fetch.polyfill = true 434 | })(); 435 | 436 | /** End of the third-party code */ 437 | module.exports = self; 438 | --------------------------------------------------------------------------------