《 JS 》简单实现 fetch & fetch 源码分享

6 篇文章 0 订阅

《 JS 》简单实现 fetch & fetch 源码分享

本文摘要,JS 原生简单实现 fetch实例,与 fetch 源码分享;

一、fetch 构成分析

ajax(基于原生 xmlHttpRequest 实现) + Promise

二、fetch 简单实现

  1. ajax 简单实现

    function ajax(url,suc,fail) {
      var xhr = new XMLHttpRequest();
        xhr.open('GET',url, true);
        xhr.onreadystatechange = function () {
            if(xhr.readyState == 4){
                if(xhr.status == 200){
                    suc(xhr.responseText)
                } else {
                    console.log(err);
                    fail(xhr.responseText);
                }
            }
        };
        xhr.send(null);
    }
    
  2. Promise 简单实现

    function Promise(fn) {
        this.resolveFn = null;
        this.rejectFn = null;
        var _this = this;
        function resolve(data) {
            var f = _this.resolveFn;
            f(data);
        }
        function reject(err) {
            var f = this.rejectFn;
            f(err);
        }
        fn(resolve,reject);
    }
    
    Promise.prototype.then = function (f) {
        this.resolveFn = f;
        return this;
    };
    Promise.prototype.catch = function (f) {
        this.rejectFn = f;
        return this;
    };
    
  3. fetch 简单实现

    function fetch(url) {
     	return new Promise(function (resolve, reject) {
            ajax(url, function (res) {
                resolve(res);
            },function (err) {
                console.log(err);
                reject(err);
            })
        })
    }
    

三、fetch 简单应用

fetch('./test.json')
  .then(function(red) {
    return res.json();
  })
  .then(function(test) {
    console.log(test);
  });

四、fetch 源码

// 一些功能的检测
var support = {
  searchParams: 'URLSearchParams' in self,      //queryString处理函数
  iterable: 'Symbol' in self && 'iterator' in Symbol,
  blob:
    'FileReader' in self &&
    'Blob' in self &&
    (function() {
      try {
        new Blob()
        return true
      } catch (e) {
        return false
      }
    })(),
  formData: 'FormData' in self,
  arrayBuffer: 'ArrayBuffer' in self       //二进制存储
}
 
function isDataView(obj) {
  return obj && DataView.prototype.isPrototypeOf(obj)
}
// 支持的 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的。
  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) || 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
}
 
// Build a destructive iterator for the value list
// 枚举器
function iteratorFor(items) {
  var iterator = {
    next: function() {
      var value = items.shift()
      return {done: value === undefined, value: value}
    }
  }
 
  if (support.iterable) {
    iterator[Symbol.iterator] = function() {
      return iterator
    }
  }
 
  return iterator
}
// 封装的Headers。
export function Headers(headers) {
     // headers最终存储的地方。
  this.map = {}
 
  if (headers instanceof Headers) {
     // 如果已经是Headers的实例,复制键值。
    headers.forEach(function(value, name) {
      this.append(name, value)
    }, this)
  } else if (Array.isArray(headers)) {
    headers.forEach(function(header) {
      this.append(header[0], header[1])
    }, this)   // this修改forEach执行函数上下文为当前上下文,就可以直接调用append方法了。
  } else if (headers) {
    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]
  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,值为value的Header。
Headers.prototype.set = function(name, value) {
  this.map[normalizeName(name)] = normalizeValue(value)
}
// 遍历Headers.
Headers.prototype.forEach = function(callback, thisArg) {
  for (var name in this.map) {
    if (this.map.hasOwnProperty(name)) {
      callback.call(thisArg, this.map[name], name, this)
    }
  }
}
 
Headers.prototype.keys = function() {
  var items = []
  this.forEach(function(value, name) {
    items.push(name)
  })
  return iteratorFor(items)
}
 
Headers.prototype.values = function() {
  var items = []
  this.forEach(function(value) {
    items.push(value)
  })
  return iteratorFor(items)
}
 
Headers.prototype.entries = function() {
  var items = []
  this.forEach(function(value, name) {
    items.push([name, value])
  })
  return iteratorFor(items)
}
 
if (support.iterable) {
  Headers.prototype[Symbol.iterator] = Headers.prototype.entries
}
 
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对象。
function readBlobAsArrayBuffer(blob) {
  var reader = new FileReader()
  var promise = fileReaderReady(reader)
  reader.readAsArrayBuffer(blob)
  return promise
}
// 读取blob为文本。
function readBlobAsText(blob) {
  var reader = new FileReader()
  var promise = fileReaderReady(reader)
  reader.readAsText(blob)
  return promise
}
 
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
  }
}
 
function Body() {
  this.bodyUsed = false
 
  this._initBody = function(body) {
    this._bodyInit = 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()
    } else if (support.arrayBuffer && support.blob && isDataView(body)) {
      this._bodyArrayBuffer = bufferClone(body.buffer)
      // IE 10-11 can't handle a DataView body.
      this._bodyInit = new Blob([this._bodyArrayBuffer])
    } else if (support.arrayBuffer && (ArrayBuffer.prototype.isPrototypeOf(body) || isArrayBufferView(body))) {
      this._bodyArrayBuffer = bufferClone(body)
    } else {
      this._bodyText = body = Object.prototype.toString.call(body)
    }
 
    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) {
    this.blob = function() {
      var rejected = consumed(this)
      if (rejected) {
        return rejected
      }
 
      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]))
      }
    }
 
    this.arrayBuffer = function() {
      if (this._bodyArrayBuffer) {
        return consumed(this) || Promise.resolve(this._bodyArrayBuffer)
      } else {
        return this.blob().then(readBlobAsArrayBuffer)
      }
    }
  }
 
  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)
    }
  }
 
  if (support.formData) {
    this.formData = function() {
      return this.text().then(decode)
    }
  }
 
  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
}
 
export function Request(input, options) {
  options = options || {}
  var body = options.body
 
  if (input instanceof Request) {
    if (input.bodyUsed) {
      throw new TypeError('Already read')
    }
    this.url = input.url
    this.credentials = input.credentials
    if (!options.headers) {
      this.headers = new Headers(input.headers)
    }
    this.method = input.method
    this.mode = input.mode
    this.signal = input.signal
    if (!body && input._bodyInit != null) {
      body = input._bodyInit
      input.bodyUsed = true
    }
  } else {
    this.url = String(input)
  }
 
  this.credentials = options.credentials || this.credentials || 'same-origin'
  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
  this.signal = options.signal || this.signal
  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)
}
 
Request.prototype.clone = function() {
  return new Request(this, {body: this._bodyInit})
}
 
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
}
 
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)
 
export 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
  this.statusText = 'statusText' in options ? options.statusText : 'OK'
  this.headers = new Headers(options.headers)
  this.url = options.url || ''
  this._initBody(bodyInit)
}
 
Body.call(Response.prototype)
 
Response.prototype.clone = function() {
  return new Response(this._bodyInit, {
    status: this.status,
    statusText: this.statusText,
    headers: new Headers(this.headers),
    url: this.url
  })
}
 
Response.error = function() {
  var response = new Response(null, {status: 0, statusText: ''})
  response.type = 'error'
  return response
}
 
var redirectStatuses = [301, 302, 303, 307, 308]
 
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}})
}
 
export var DOMException = self.DOMException
try {
  new DOMException()
} catch (err) {
  DOMException = function(message, name) {
    this.message = message
    this.name = name
    var error = Error(message)
    this.stack = error.stack
  }
  DOMException.prototype = Object.create(Error.prototype)
  DOMException.prototype.constructor = DOMException
}
 
export function fetch(input, init) {
  return new Promise(function(resolve, reject) {
    var request = new Request(input, init)
 
    if (request.signal && request.signal.aborted) {
      return reject(new DOMException('Aborted', 'AbortError'))
    }
 
    var xhr = new XMLHttpRequest()
 
    function abortXhr() {
      xhr.abort()
    }
 
    xhr.onload = function() {
      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))
    }
 
    xhr.onerror = function() {
      reject(new TypeError('Network request failed'))
    }
 
    xhr.ontimeout = function() {
      reject(new TypeError('Network request failed'))
    }
 
    xhr.onabort = function() {
      reject(new DOMException('Aborted', 'AbortError'))
    }
 
    xhr.open(request.method, request.url, true)
 
    if (request.credentials === 'include') {
      xhr.withCredentials = true
    } else if (request.credentials === 'omit') {
      xhr.withCredentials = false
    }
 
    if ('responseType' in xhr && support.blob) {
      xhr.responseType = 'blob'
    }
 
    request.headers.forEach(function(value, name) {
      xhr.setRequestHeader(name, value)
    })
 
    if (request.signal) {
      request.signal.addEventListener('abort', abortXhr)
 
      xhr.onreadystatechange = function() {
        // DONE (success or failure)
        if (xhr.readyState === 4) {
          request.signal.removeEventListener('abort', abortXhr)
        }
      }
    }
 
    xhr.send(typeof request._bodyInit === 'undefined' ? null : request._bodyInit)
  })
}
 
fetch.polyfill = true
 
 // self 可理解为 window 对象
if (!self.fetch) {
  self.fetch = fetch      
  self.Headers = Headers
  self.Request = Request
  self.Response = Response
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
基于web的网络教学平台的设计与实现码是一个复杂的项目,需要涉及到前端设计、后端开发以及数据库管理等多方面的工作。 首先,前端设计方面,我们需要使用HTML、CSS和JavaScript等技术来设计网站的页面布局、样式和交互效果。我们可以利用现有的前端框架,如Bootstrap或者React等来简化页面设计和提高用户体验。 其次,后端开发方面,我们需要使用一种后端语言,如Java、Python或者Node.js等来实现网站的业务逻辑。同时,我们还需要使用数据库来存储用户信息、课程内容、作业等数据。可以选择关系型数据库,如MySQL或者PostgreSQL,也可以使用非关系型数据库,如MongoDB等。 在实现码方面,我们需要将前端和后端的代码进行整合,并确保它们之间可以正常通信。我们需要使用HTTP协议来实现前后端的数据传输,同时使用一些框架或者库来简化网络请求的处理,如Axios或者Fetch等。 另外,为了确保网站的安全性和稳定性,我们还需要实现用户认证、权限管理、数据加密等功能。可以使用一些现成的安全库来简化这些工作,如JWT、OAuth2等。 总之,基于web的网络教学平台的设计与实现码是一个综合性的项目,需要涉及到多方面的技术和工作。在这个过程中,我们需要不断地学习和探索,才能够设计出一个功能完善、性能优越的网络教学平台。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值