zoukankan      html  css  js  c++  java
  • whatwg-fetch源码分析

    fetch 是什么

    XMLHttpRequest的最新替代技术 

    fetch优点

    • 接口更简单、简洁,更加语义化
    • 基于promise,更加好的流程化控制,可以不断then把参数传递,外加 async/await,异步变同步的代码书写风格
    • 利于同构,isomorphic-fetch 是对 whatwg-fetch和node-fetch的一种封装,你一份代码就可以在两种环境下跑起来了
    • 新的web api很多内置支持fetch,比如 service worker

    fetch 缺点

    • 兼容性
    • 不支持progress事件(可以借助 response.body.getRender方法来实现)
    • 默认不带cookie
    • 某些错误的http状态下如400、500等不会reject,相反它会被resolve
    • 不支持timeout处理
    • 不支持jsonp,当然可以引入 fetch-jsonp 来支持

    这些缺点,后面的参考里面有各种解决方案

    fetch兼容性(2017-08-08):

     fetch是基于promise设计的,

     fetch参数

      参考 Fetch Standard 或者 Using Fetch

     上面你对fetch有基本的了解了,而且提供了不少的链接解惑,那么我们进入正题,whatwg-fetch源码分析 

     依旧是先删除无用的代码,

    (function (self) {
      'use strict';  
      if (self.fetch) {
         return
      }
    
      // 封装的 Headers,支持的方法参考https://developer.mozilla.org/en-US/docs/Web/API/Headers
      function Headers(headers) {
        ......
      }  
    
      //方法参考:https://developer.mozilla.org/en-US/docs/Web/API/Body
      function Body() { 
        ......
      }
    
      // 请求的Request对象 ,https://developer.mozilla.org/en-US/docs/Web/API/Request
      // cache,context,integrity,redirect,referrerPolicy 在MDN定义中是存在的
      function Request(input, options) {
         ......
      }
    
      Body.call(Request.prototype)  //把Body方法属性绑到 Reques.prototype
      
      function Response(bodyInit, options) {   
      }
    
      Body.call(Response.prototype) //把Body方法属性绑到 Reques.prototype
    
      self.Headers = Headers  //暴露Headers
      self.Request = Request //暴露Request
      self.Response = Response //暴露Response
    
      self.fetch = function (input, init) {
        return new Promise(function (resolve, reject) {
          var request = new Request(input, init)  //初始化request对象
          var xhr = new XMLHttpRequest()  // 初始化 xhr
    
          xhr.onload = function () { //请求成功,构建Response,并resolve进入下一阶段
            var options = {
              status: xhr.status,
              statusText: xhr.statusText,
              headers: parseHeaders(xhr.getAllResponseHeaders() || '')
            }
            options.url = 'responseURL' in xhr ? xhr.responseURL : options.headers.get('X-Request-URL')
            var body = 'response' in xhr ? xhr.response : xhr.responseText
            resolve(new Response(body, options))
          }
    
          //请求失败,构建Error,并reject进入下一阶段
          xhr.onerror = function () {
            reject(new TypeError('Network request failed'))
          }
    
          //请求超时,构建Error,并reject进入下一阶段
          xhr.ontimeout = function () {
            reject(new TypeError('Network request failed'))
          }
    
          // 设置xhr参数
          xhr.open(request.method, request.url, true)
    
          // 设置 credentials 
          if (request.credentials === 'include') {
            xhr.withCredentials = true
          } else if (request.credentials === 'omit') {
            xhr.withCredentials = false
          }
    
          // 设置 responseType
          if ('responseType' in xhr && support.blob) {
            xhr.responseType = 'blob'
          }
    
          // 设置Header
          request.headers.forEach(function (value, name) {
            xhr.setRequestHeader(name, value)
          })
          // 发送请求
          xhr.send(typeof request._bodyInit === 'undefined' ? null : request._bodyInit)
        })
      }
      //标记是fetch是polyfill的,而不是原生的
      self.fetch.polyfill = true
    })(typeof self !== 'undefined' ? self : this); // IIFE函数的参数,不用window,web worker, service worker里面也可以使用

     简单分析一下

    • 如果自身支持fetch,直接返回,用自身的
    • 内部核心 Headers, Body, Request, Response,
      • Request和Resonse原型上有Body的方法属性,或者说,继承了
      • Headers,Request ,Reponse暴露到全局 
    • fetch本质就是对XMLHttpRequest 请求的封装  

     这么一看其实到没什么了,不过完整代码里面有一些东西还是提一下(后面的参考都有链接)

    • Symbol, Iterator : ES6里面很多集合是自带默认Iterator的,作用就是在 let...of,数组解构,新Set,Map初始化等情况会被调用。
    • DataView , TypedArray:都是对TypeArray读写的API
    • Blob,FileReader :File API,这个也没啥多说的  
    • URLSearchParams: 这个支持度还不高,用来解析和构建 URL Search 参数的,例如  new URLSearchParams(window.location.search).get('a')

     对外暴露的对象或者方法有

    • fetch

       封装过后的fetch,关于参数和使用 

    • Headers

         http请求头,属性方法和使用

    • Request

          请求对象属性方法和使用

    • Response

       请求的响应对象,属性方法和使用

    这面重点解析几个重点函数和方法,其他的相对容易

    iteratorFor

     在定义中,Headers实例,headers.keys(), headers.values(), headers.entries()返回的都是Iterator, 下面代码读起来可能有点绕,

     你这样理解,定义iterator 是保证能使用next方法来遍历 

    定义iterator[Symbol.iterator] 是设置默认 Iterator,能使用 let...of,Array.from,数组解构等相对高级一些方法访问到

      // 枚举器, http://es6.ruanyifeng.com/#docs/iterator
      // 觉得可以如下 ,同样支持 next() 和 for ...of 等形式访问 ,之后才是不支持iterable的情况,添加next方法来访问
      //  if ((support.iterable && items[Symbol.iterator]) {
      //   return items[Symbol.iterator]()
      // }
      function iteratorFor(items) {
        // 这里你就可以 res.headers.keys().next().value这样调用
        var iterator = {
          next: function () {
            var value = items.shift()
            return { done: value === undefined, value: value }
          }
        }
    
        if (support.iterable) {
          // 添加默认Iterator
          // for...of,解构赋值,扩展运算符,yield*,Map(), Set(), WeakMap(), WeakSet(),Promise.all(),Promise.race()都会调用默认Iterator     
          iterator[Symbol.iterator] = function () {
            return iterator
          }
        }
    
        // 到这里就支持了两种访问形式了
        // res.headers.keys().next().value
        // for(let key in headers.keys())
        return iterator
      }

    Body.call

    实现继承,把body的方法属性绑定指定对象原型

    Body.call(Request.prototype) 
    Body.call(Response.prototype) 

     这两个理解上,就基本可以无大碍了,那我贴出完整带注释的代码

    (function (self) {
      'use strict';
    
      //如果自身支持fetch,直接返回原生的fetch
      if (self.fetch) {
        // return
      }
    
      // 一些功能检测
      var support = {
        searchParams: 'URLSearchParams' in self, // queryString 处理函数,https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams,http://caniuse.com/#search=URLSearchParams
        iterable: 'Symbol' in self && 'iterator' in Symbol,  // Symbol(http://es6.ruanyifeng.com/#docs/symbol)E6新数据类型,表示独一无二的值 和 iterator枚举
        blob: 'FileReader' in self && 'Blob' in self && (function () {
          try {
            new Blob()
            return true
          } catch (e) {
            return false
          }
        })(),  // Blob 和 FileReader
        formData: 'FormData' in self, // FormData
        arrayBuffer: 'ArrayBuffer' in self // ArrayBuffer 二进制数据存储
      }
    
      // 支持的 ArrayBuffer类型
      if (support.arrayBuffer) {
        var viewClasses = [
          '[object Int8Array]',
          '[object Uint8Array]',
          '[object Uint8ClampedArray]',
          '[object Int16Array]',
          '[object Uint16Array]',
          '[object Int32Array]',
          '[object Uint32Array]',
          '[object Float32Array]',
          '[object Float64Array]'
        ]
    
        // 检查是不是DataView,DataView是来读写ArrayBuffer的 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView
        var isDataView = function (obj) {
          return obj && DataView.prototype.isPrototypeOf(obj)
        }
    
        // 检查是不是有效的ArrayBuffer view,TypedArray均返回true ArrayBuffer.isView(new ArrayBuffer(10)) 为false, https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer/isView
        var isArrayBufferView = ArrayBuffer.isView || function (obj) {
          return obj && viewClasses.indexOf(Object.prototype.toString.call(obj)) > -1
        }
      }
    
      // 检查header name,并转为小写
      function normalizeName(name) {
        // 不是字符串,转为字符串
        if (typeof name !== 'string') {
          name = String(name)
        }
        // 不以 a-z 0-9 -#$%*+.^_`|~ 开头,抛出错误
        if (/[^a-z0-9-#$%&'*+.^_`|~]/i.test(name)) {
          throw new TypeError('Invalid character in header field name')
        }
        //转为小写
        return name.toLowerCase()
      }
    
      // 转换header的值
      function normalizeValue(value) {
        if (typeof value !== 'string') {
          value = String(value)
        }
        return value
      }
    
      // 枚举器, http://es6.ruanyifeng.com/#docs/iterator
      // 觉得可以如下 ,同样支持 next() 和 for ...of 等形式访问 ,之后才是不支持iterable的情况,添加next方法来访问
      //  if ((support.iterable && items[Symbol.iterator]) {
      //   return items[Symbol.iterator]()
      // }
      function iteratorFor(items) {
        // 这里你就可以 res.headers.keys().next().value这样调用
        var iterator = {
          next: function () {
            var value = items.shift()
            return { done: value === undefined, value: value }
          }
        }
    
        if (support.iterable) {
          // 添加默认Iterator
          // for...of,解构赋值,扩展运算符,yield*,Map(), Set(), WeakMap(), WeakSet(),Promise.all(),Promise.race()都会调用默认Iterator     
          iterator[Symbol.iterator] = function () {
            return iterator
          }
        }
    
        // 到这里就支持了两种访问形式了
        // res.headers.keys().next().value
        // for(let key in headers.keys())
        return iterator
      }
    
      // 封装的 Headers,支持的方法参考https://developer.mozilla.org/en-US/docs/Web/API/Headers
      function Headers(headers) {
        this.map = {} // headers 最终存储的地方
    
        if (headers instanceof Headers) { // 如果已经是 Headers的实例,复制键值
          headers.forEach(function (value, name) {
            this.append(name, value)
          }, this) // this修改forEach执行函数上下文为当前上下文,就可以直接调用append方法了
        } else if (Array.isArray(headers)) { // 如果是数组,[['Content-Type':''],['Referer','']]
          headers.forEach(function (header) {
            this.append(header[0], header[1])
          }, this)
        } else if (headers) {
          // 对象  {'Content-Type':'',Referer:''}
          Object.getOwnPropertyNames(headers).forEach(function (name) {
            this.append(name, headers[name])
          }, this)
        }
      }
    
      // 添加或者追加Header
      Headers.prototype.append = function (name, value) {
        name = normalizeName(name)
        value = normalizeValue(value)
        var oldValue = this.map[name]
        // 支持 append, 比如 Accept:text/html ,后来 append('Accept','application/xhtml+xml') 那么最终  Accept:'text/html,application/xhtml+xml'
        this.map[name] = oldValue ? oldValue + ',' + value : value
      }
    
      //删除名为name的Header
      Headers.prototype['delete'] = function (name) {
        delete this.map[normalizeName(name)]
      }
    
      //获得名为Name的Header
      Headers.prototype.get = function (name) {
        name = normalizeName(name)
        return this.has(name) ? this.map[name] : null
      }
    
      //查询时候有名为name的Header
      Headers.prototype.has = function (name) {
        return this.map.hasOwnProperty(normalizeName(name))
      }
      //设置或者覆盖名为name,值为vaue的Header
      Headers.prototype.set = function (name, value) {
        this.map[normalizeName(name)] = normalizeValue(value)
      }
      //遍历Headers
      Headers.prototype.forEach = function (callback, thisArg) {
        //遍历属性   
        //我觉得也挺不错 Object.getOwnPropertyNames(this.map).forEach(function(name){ callback.call(thisArg, this.map[name], name, this) },this)
        for (var name in this.map) {
          //检查是不是自己的属性
          if (this.map.hasOwnProperty(name)) {
            //调用
            callback.call(thisArg, this.map[name], name, this)
          }
        }
      }
    
      // 所有的键,keys, values, entries, res.headers返回的均是 iterator 
      Headers.prototype.keys = function () {
        var items = []
        this.forEach(function (value, name) { items.push(name) })
        return iteratorFor(items)
      }
      // 所有的值,keys, values, entries, res.headers返回的均是 iterator 
      Headers.prototype.values = function () {
        var items = []
        this.forEach(function (value) { items.push(value) })
        return iteratorFor(items)
      }
      // 所有的entries,格式是这样 [[name1,value1],[name2,value2]],keys, values, entries, res.headers返回的均是 iterator 
      Headers.prototype.entries = function () {
        var items = []
        this.forEach(function (value, name) { items.push([name, value]) })
        return iteratorFor(items)
      }
    
      //设置Headers原型默认的Iterator,keys, values, entries, res.headers返回的均是 iterator 
      if (support.iterable) {
        Headers.prototype[Symbol.iterator] = Headers.prototype.entries
      }
    
      //是否已经消费/读取过,如果读取过,会直接到catch或者error处理函数
      function consumed(body) {
        if (body.bodyUsed) {
          return Promise.reject(new TypeError('Already read'))
        }
        body.bodyUsed = true
      }
    
      // FileReader读取完毕
      function fileReaderReady(reader) {
        return new Promise(function (resolve, reject) {
          reader.onload = function () {
            resolve(reader.result)
          }
          reader.onerror = function () {
            reject(reader.error)
          }
        })
      }
    
      // 读取blob为ArrayBuffer对象,https://www.w3.org/TR/FileAPI/#dfn-filereader
      function readBlobAsArrayBuffer(blob) {
        var reader = new FileReader()
        var promise = fileReaderReady(reader)
        reader.readAsArrayBuffer(blob)
        return promise
      }
      // 读取blob为文本,https://www.w3.org/TR/FileAPI/#dfn-filereader
      function readBlobAsText(blob) {
        var reader = new FileReader()
        var promise = fileReaderReady(reader)
        reader.readAsText(blob)
        return promise
      }
    
      // ArrayBuffer读为文本
      function readArrayBufferAsText(buf) {
        var view = new Uint8Array(buf)
        var chars = new Array(view.length)
    
        for (var i = 0; i < view.length; i++) {
          chars[i] = String.fromCharCode(view[i])
        }
        return chars.join('')
      }
    
      //克隆ArrayBuffer
      function bufferClone(buf) {
        if (buf.slice) {  //支持 slice,直接slice(0)复制,数据基本都是这样复制的
          return buf.slice(0)
        } else {
          //新建填充模式复制
          var view = new Uint8Array(buf.byteLength)
          view.set(new Uint8Array(buf))
          return view.buffer
        }
      }
    
      //方法参考:https://developer.mozilla.org/en-US/docs/Web/API/Body
      function Body() {
        this.bodyUsed = false
    
        this._initBody = function (body) {
          // 把最原始的数据存下来
          this._bodyInit = body
          // 判断body数据类型,然后存下来
          if (!body) {
            this._bodyText = ''
          } else if (typeof body === 'string') {
            this._bodyText = body
          } else if (support.blob && Blob.prototype.isPrototypeOf(body)) {
            this._bodyBlob = body
          } else if (support.formData && FormData.prototype.isPrototypeOf(body)) {
            this._bodyFormData = body
          } else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) {
            this._bodyText = body.toString()   //数据格式是这样的 a=1&b=2&c=3
          } else if (support.arrayBuffer && support.blob && isDataView(body)) {
            // ArrayBuffer一般是通过DataView或者各种Float32Array,Uint8Array来操作的, https://hacks.mozilla.org/2017/01/typedarray-or-dataview-understanding-byte-order/
            // 如果是DataView, DataView的数据是存在 DataView.buffer上的
            this._bodyArrayBuffer = bufferClone(body.buffer)  // 复制ArrayBuffer
            // IE 10-11 can't handle a DataView body.
            this._bodyInit = new Blob([this._bodyArrayBuffer]) // 重新设置_bodyInt
          } else if (support.arrayBuffer && (ArrayBuffer.prototype.isPrototypeOf(body) || isArrayBufferView(body))) {
            // ArrayBuffer一般是通过DataView或者各种Float32Array,Uint8Array来操作的, 
            // https://hacks.mozilla.org/2017/01/typedarray-or-dataview-understanding-byte-order/
            this._bodyArrayBuffer = bufferClone(body)
          } else {
            throw new Error('unsupported BodyInit type')
          }
    
          // 设置content-type
          if (!this.headers.get('content-type')) {
            if (typeof body === 'string') {
              this.headers.set('content-type', 'text/plain;charset=UTF-8')
            } else if (this._bodyBlob && this._bodyBlob.type) {
              this.headers.set('content-type', this._bodyBlob.type)
            } else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) {
              this.headers.set('content-type', 'application/x-www-form-urlencoded;charset=UTF-8')
            }
          }
        }
    
        if (support.blob) {
          // 使用 fetch(...).then(res=>res.blob())
          this.blob = function () {
            //标记为已经使用
            var rejected = consumed(this)
            if (rejected) {
              return rejected
            }
    
            // resolve,进入then
            if (this._bodyBlob) {
              return Promise.resolve(this._bodyBlob)
            } else if (this._bodyArrayBuffer) {
              return Promise.resolve(new Blob([this._bodyArrayBuffer]))
            } else if (this._bodyFormData) {
              throw new Error('could not read FormData body as blob')
            } else {
              return Promise.resolve(new Blob([this._bodyText]))
            }
          }
          // 使用 fetch(...).then(res=>res.arrayBuffer())
          this.arrayBuffer = function () {
            if (this._bodyArrayBuffer) {
              return consumed(this) || Promise.resolve(this._bodyArrayBuffer)
            } else {
              return this.blob().then(readBlobAsArrayBuffer) //如果有blob,读取成ArrayBuffer
            }
          }
        }
    
        // 使用 fetch(...).then(res=>res.text())
        this.text = function () {
          var rejected = consumed(this)
          if (rejected) {
            return rejected
          }
    
          if (this._bodyBlob) {
            return readBlobAsText(this._bodyBlob)
          } else if (this._bodyArrayBuffer) {
            return Promise.resolve(readArrayBufferAsText(this._bodyArrayBuffer))
          } else if (this._bodyFormData) {
            throw new Error('could not read FormData body as text')
          } else {
            return Promise.resolve(this._bodyText)
          }
        }
    
        // 使用 fetch(...).then(res=>res.formData())
        if (support.formData) {
          this.formData = function () {
            return this.text().then(decode)
          }
        }
    
        // 使用 fetch(...).then(res=>res.json())
        this.json = function () {
          return this.text().then(JSON.parse)
        }
    
        return this
      }
    
      // HTTP methods whose capitalization should be normalized
      var methods = ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST', 'PUT']
    
      // 方法名大写
      function normalizeMethod(method) {
        var upcased = method.toUpperCase()
        return (methods.indexOf(upcased) > -1) ? upcased : method
      }
    
      // 请求的Request对象 ,https://developer.mozilla.org/en-US/docs/Web/API/Request
      // cache,context,integrity,redirect,referrerPolicy 在MDN定义中是存在的
      function Request(input, options) {
        options = options || {}
        var body = options.body
    
        //如果已经是Request的实例,解析赋值
        if (input instanceof Request) {    
          if (input.bodyUsed) {
            throw new TypeError('Already read')
          }
          this.url = input.url  //请求的地址
          this.credentials = input.credentials  //登陆凭证
          if (!options.headers) { //headers
            this.headers = new Headers(input.headers) 
          }
          this.method = input.method  //请求方法 GET,POST......
          this.mode = input.mode      // same-origin,cors,no-cors
          if (!body && input._bodyInit != null) { //标记Request已经使用
            body = input._bodyInit
            input.bodyUsed = true
          }
        } else {
          this.url = String(input)
        }
    
        this.credentials = options.credentials || this.credentials || 'omit'
        if (options.headers || !this.headers) {
          this.headers = new Headers(options.headers)
        }
        this.method = normalizeMethod(options.method || this.method || 'GET')
        this.mode = options.mode || this.mode || null //same-origin,cors,no-cors
        this.referrer = null
    
        if ((this.method === 'GET' || this.method === 'HEAD') && body) {
          throw new TypeError('Body not allowed for GET or HEAD requests')
        }
        this._initBody(body)  //解析值 和设置content-type
      }
    
      // 克隆
      Request.prototype.clone = function () {
        return new Request(this, { body: this._bodyInit })
      }
    
      // body存为 FormData
      function decode(body) {
        var form = new FormData()
        body.trim().split('&').forEach(function (bytes) {
          if (bytes) {
            var split = bytes.split('=')
            var name = split.shift().replace(/+/g, ' ')
            var value = split.join('=').replace(/+/g, ' ')
            form.append(decodeURIComponent(name), decodeURIComponent(value))
          }
        })
        return form
      }
    
      // 用于接续 xhr.getAllResponseHeaders, 数据格式
      //Cache-control: private
      //Content-length:554
      function parseHeaders(rawHeaders) {
        var headers = new Headers()
        // Replace instances of 
     and 
     followed by at least one space or horizontal tab with a space
        // https://tools.ietf.org/html/rfc7230#section-3.2
        var preProcessedHeaders = rawHeaders.replace(/
    ?
    [	 ]+/g, ' ')
        preProcessedHeaders.split(/
    ?
    /).forEach(function (line) {
          var parts = line.split(':')
          var key = parts.shift().trim()
          if (key) {
            var value = parts.join(':').trim()
            headers.append(key, value)
          }
        })
        return headers
      }
    
      Body.call(Request.prototype)  //把Body方法属性绑到 Reques.prototype
    
      // Reponse对象,https://developer.mozilla.org/en-US/docs/Web/API/Response
      function Response(bodyInit, options) {
        if (!options) {
          options = {}
        }
    
        this.type = 'default'
        this.status = options.status === undefined ? 200 : options.status
        this.ok = this.status >= 200 && this.status < 300  // 200 - 300 ,https://developer.mozilla.org/en-US/docs/Web/API/Response/ok
        this.statusText = 'statusText' in options ? options.statusText : 'OK'
        this.headers = new Headers(options.headers)
        this.url = options.url || ''
        this._initBody(bodyInit) // 解析值和设置content-type
      }
    
      Body.call(Response.prototype) //把Body方法属性绑到 Reques.prototype
    
      // 克隆Response
      Response.prototype.clone = function () {
        return new Response(this._bodyInit, {
          status: this.status,
          statusText: this.statusText,
          headers: new Headers(this.headers),
          url: this.url
        })
      }
    
      //返回一个 error性质的Response,静态方法
      Response.error = function () {
        var response = new Response(null, { status: 0, statusText: '' })
        response.type = 'error'
        return response
      }
    
      var redirectStatuses = [301, 302, 303, 307, 308]
    
      // 重定向,本身并不产生实际的效果,静态方法,https://developer.mozilla.org/en-US/docs/Web/API/Response/redirect
      Response.redirect = function (url, status) {
        if (redirectStatuses.indexOf(status) === -1) {
          throw new RangeError('Invalid status code')
        }
    
        return new Response(null, { status: status, headers: { location: url } })
      }
    
      self.Headers = Headers  //暴露Headers
      self.Request = Request //暴露Request
      self.Response = Response //暴露Response
    
      self.fetch = function (input, init) {
        return new Promise(function (resolve, reject) {
          var request = new Request(input, init)  //初始化request对象
          var xhr = new XMLHttpRequest()  // 初始化 xhr
    
          xhr.onload = function () { //请求成功,构建Response,并resolve进入下一阶段
            var options = {
              status: xhr.status,
              statusText: xhr.statusText,
              headers: parseHeaders(xhr.getAllResponseHeaders() || '')
            }
            options.url = 'responseURL' in xhr ? xhr.responseURL : options.headers.get('X-Request-URL')
            var body = 'response' in xhr ? xhr.response : xhr.responseText
            resolve(new Response(body, options))
          }
    
          //请求失败,构建Error,并reject进入下一阶段
          xhr.onerror = function () {
            reject(new TypeError('Network request failed'))
          }
    
          //请求超时,构建Error,并reject进入下一阶段
          xhr.ontimeout = function () {
            reject(new TypeError('Network request failed'))
          }
    
          // 设置xhr参数
          xhr.open(request.method, request.url, true)
    
          // 设置 credentials 
          if (request.credentials === 'include') {
            xhr.withCredentials = true
          } else if (request.credentials === 'omit') {
            xhr.withCredentials = false
          }
    
          // 设置 responseType
          if ('responseType' in xhr && support.blob) {
            xhr.responseType = 'blob'
          }
    
          // 设置Header
          request.headers.forEach(function (value, name) {
            xhr.setRequestHeader(name, value)
          })
          // 发送请求
          xhr.send(typeof request._bodyInit === 'undefined' ? null : request._bodyInit)
        })
      }
      //标记是fetch是polyfill的,而不是原生的
      self.fetch.polyfill = true
    })(typeof self !== 'undefined' ? self : this); // IIFE函数的参数,不用window,web worker, service worker里面也可以使用

     

    小结:

    • 可以看出,有些属性是没有实现的,但是一般的请求足以
    • Response.body 这种ReadableStream没有实现,自然就没有fetch原生处理progress的方法    
    fetch('/').then(response => {
      // response.body is a readable stream.
      // Calling getReader() gives us exclusive access to the stream's content
      var reader = response.body.getReader();
      var bytesReceived = 0;
    
      // read() returns a promise that resolves when a value has been received
      reader.read().then(function processResult(result) {
        // Result objects contain two properties:
        // done  - true if the stream has already given you all its data.
        // value - some data. Always undefined when done is true.
        if (result.done) {
          console.log("Fetch complete");
          return;
        }
    
        // result.value for fetch streams is a Uint8Array
        bytesReceived += result.value.length;
        console.log('Received', bytesReceived, 'bytes of data so far');
    
        // Read some more, and call this function again
        return reader.read().then(processResult);
      });
    });

    参考:

    使用fetch遇到过的坑

    fetch使用的常见问题及解决办法

    Fetch Standard

    Fetch相比Ajax有什么优势

    Fetch API

    Iterator 

    URLSearchParams - Web APIs | MDN

  • 相关阅读:
    python知识点整理一
    Selenium之自动化常遇问题
    git学习
    哈,又是总结内容
    2.3返回IP地址(requests模块安装,get请求发送,loads 解析json到字典)
    2.2返回状态码的分类描述
    2.1JSON数据格式
    1.6file文件
    福利:字符串与字符编码
    1.5list中sort && sorted
  • 原文地址:https://www.cnblogs.com/cloud-/p/7305857.html
Copyright © 2011-2022 走看看