whatwg-fetch源碼分析

fetch 是什麼

XMLHttpRequest的最新替代技術 html

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 來支持

這些缺點,後面的參考裏面有各類解決方案node

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

 fetch是基於promise設計的,git

 fetch參數

  參考 Fetch Standard 或者 Using Fetches6

 

 上面你對fetch有基本的瞭解了,並且提供了很多的連接解惑,那麼咱們進入正題,whatwg-fetch源碼分析 github

 依舊是先刪除無用的代碼,web

(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裏面也可使用

 簡單分析一下ajax

  • 若是自身支持fetch,直接返回,用自身的
  • 內部核心 Headers, Body, Request, Response,
    • Request和Resonse原型上有Body的方法屬性,或者說,繼承了
    • Headers,Request ,Reponse暴露到全局 
  • fetch本質就是對XMLHttpRequest 請求的封裝  

 這麼一看其實到沒什麼了,不過完整代碼裏面有一些東西仍是提一下(後面的參考都有連接)json

  • 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')

 對外暴露的對象或者方法有api

  • 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 \r\n and \n 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(/\r?\n[\t ]+/g, ' ')
    preProcessedHeaders.split(/\r?\n/).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

相關文章
相關標籤/搜索