Skip to main content

Fetch

  • GET: read resources.
  • POST: create resources.
  • PUT: fully update resources.
  • PATCH: partially update resources.
  • DELETE: delete resources.
const response = await fetch('/api/names', {
headers: {
Accept: 'application/json',
},
})

const response = await fetch('/api/names', {
method: 'POST',
body: JSON.stringify(object),
headers: {
'Content-Type': 'application/json',
},
})

JSON

JSON (JavaScript Object Notation) methods:

const obj = JSON.parse(json)
const json = JSON.stringify(obj)

JSON.stringify(value, filter, space):

  • Symbol/function/NaN/Infinity/undefined: null/ignored.
  • BitInt: throw TypeError.
  • Circular reference object: throw TypeError.
  • toJSON method:
const obj = {
name: 'zc',
toJSON() {
return 'return toJSON'
},
}

// return toJSON
console.log(JSON.stringify(obj))

// "2022-03-06T08:24:56.138Z"
JSON.stringify(new Date())

Form Data

const imageFormData = new FormData()
const imageInput = document.querySelector('input[type="file"][multiple]')
const imageFiles = imageInput.files

for (const file of imageFiles)
imageFormData.append('image', file)

fetch('/img-upload', {
method: 'POST',
body: imageFormData,
})

Abort Controller

const abortController = new AbortController()

fetch('wikipedia.zip', { signal: abortController.signal }).catch(() =>
console.log('Aborted!')
)

// 10 毫秒后中断请求
setTimeout(() => abortController.abort(), 10)

APIs

Headers object:

const myHeaders = new Headers()
myHeaders.append('Content-Type', 'text/xml')
myHeaders.get('Content-Type') // should return 'text/xml'

Request object:

const request = new Request('/api/names', {
method: 'POST',
body: JSON.stringify(object),
headers: {
'Content-Type': 'application/json',
},
})

const response = await fetch(request)

Response object:

fetch('//foo.com').then(console.log)
// Response {
// body: (...)
// bodyUsed: false
// headers: Headers {}
// ok: true
// redirected: false
// status: 200
// statusText: "OK"
// type: "basic"
// url: "https://foo.com/"
// }

fetch('//foo.com/redirect-me').then(console.log)
// Response {
// body: (...)
// bodyUsed: false
// headers: Headers {}
// ok: true
// redirected: true
// status: 200
// statusText: "OK"
// type: "basic"
// url: "https://foo.com/redirected-url/"
// }

fetch('//foo.com/does-not-exist').then(console.log)
// Response {
// body: (...)
// bodyUsed: false
// headers: Headers {}
// ok: false
// redirected: true
// status: 404
// statusText: "Not Found"
// type: "basic"
// url: "https://foo.com/does-not-exist/"
// }

fetch('//foo.com/throws-error').then(console.log)
// Response {
// body: (...)
// bodyUsed: false
// headers: Headers {}
// ok: false
// redirected: true
// status: 500
// statusText: "Internal Server Error"
// type: "basic"
// url: "https://foo.com/throws-error/"
// }

Streaming

Request/Response body (ReadableStream) methods:

  • text().
  • json().
  • formData().
  • arrayBuffer().
  • blob().
  • bodyUsed: 布尔值, 表示 ReadableStream 是否已摄受 (disturbed).
fetch('https://fetch.spec.whatwg.org/')
.then(response => response.body)
.then((body) => {
const reader = body.getReader()

function processNextChunk({ value, done }) {
if (done)
return

console.log(value)
return reader.read().then(processNextChunk)
}

return reader.read().then(processNextChunk)
})
// { value: Uint8Array{}, done: false }
// { value: Uint8Array{}, done: false }
// { value: Uint8Array{}, done: false }
// ...

fetch('https://fetch.spec.whatwg.org/')
.then(response => response.body)
.then(async (body) => {
const reader = body.getReader()

while (true) {
const { value, done } = await reader.read()

if (done)
break

console.log(value)
}
})
// { value: Uint8Array{}, done: false }
// { value: Uint8Array{}, done: false }
// { value: Uint8Array{}, done: false }
// ...
fetch('https://fetch.spec.whatwg.org/')
.then(response => response.body)
.then(async (body) => {
const reader = body.getReader()
const asyncIterable = {
[Symbol.asyncIterator]() {
return {
next() {
return reader.read()
},
}
},
}

for await (const chunk of asyncIterable)
console.log(chunk)
})
// { value: Uint8Array{}, done: false }
// { value: Uint8Array{}, done: false }
// { value: Uint8Array{}, done: false }
// ...
async function* streamGenerator(stream) {
const reader = stream.getReader()

try {
while (true) {
const { value, done } = await reader.read()

if (done)
break

yield value
}
} finally {
reader.releaseLock()
}
}

const decoder = new TextDecoder()

fetch('https://fetch.spec.whatwg.org/')
.then(response => response.body)
.then(async (body) => {
for await (const chunk of streamGenerator(body))
console.log(decoder.decode(chunk, { stream: true }))
})
// <!doctype html><html lang="en"> ...
// whether a <a data-link-type="dfn" href="#concept-header" ...
// result to <var>rangeValue</var>. ...
// ...
fetch('https://fetch.spec.whatwg.org/')
.then(response => response.body)
.then((body) => {
const reader = body.getReader()
// 创建第二个流
return new ReadableStream({
async start(controller) {
try {
while (true) {
const { value, done } = await reader.read()

if (done)
break

// 将主体流的块推到第二个流
controller.enqueue(value)
}
} finally {
controller.close()
reader.releaseLock()
}
},
})
})
.then(secondaryStream => new Response(secondaryStream))
.then(response => response.text())
.then(console.log)
// <!doctype html><html lang="en"><head><meta charset="utf-8"> ...

Axios

  • client.request(config).
  • client.get(url[, config]).
  • client.delete(url[, config]).
  • client.head(url[, config]).
  • client.options(url[, config]).
  • client.post(url[, data[, config]]).
  • client.put(url[, data[, config]]).
  • client.patch(url[, data[, config]]).
  • client.getUri([config]).
const client = axios.create({
baseURL: 'https://some-domain.com/api/',
timeout: 1000,
headers: { 'X-Custom-Header': 'foobar' },
})

// Add a request interceptor
client.interceptors.request.use(
(config) => {
// Do something before request is sent.
return config
},
(error) => {
// Do something with request error.
return Promise.reject(error)
}
)

client.interceptors.response.use(
(response) => {
// Any status code that lie within the range of 2xx trigger this function.
// Do something with response data.
return response
},
(error) => {
// Any status codes that falls outside the range of 2xx trigger this function.
// Do something with response error.
return Promise.reject(error)
}
)

AJAX

const XHR = (function () {
const standard = {
createXHR() {
return new XMLHttpRequest()
},
}
const newActionXObject = {
createXHR() {
return new ActionXObject('Msxml12.XMLHTTP')
},
}
const oldActionXObject = {
createXHR() {
return new ActionXObject('Microsoft.XMLHTTP')
},
}

// 根据兼容性返回对应的工厂对象
// 此立即函数运行一次即可完成兼容性检查, 防止重复检查
if (standard.createXHR()) {
return standard
} else {
try {
newActionXObject.createXHR()
return newActionXObject
} catch (o) {
oldActionXObject.createXHR()
return oldActionXObject
}
}
})()

const request = XHR.createXHR()

// 3rd argument : async mode
request.open('GET', 'example.txt', true)

request.onreadystatechange = function () {
// do something
/*
switch(request.readyState) {
case 0: initialize
case 1: loading
case 2: loaded
case 3: transaction
case 4: complete
}
*/
if (request.readyState === 4) {
const para = document.createElement('p')
const txt = document.createTextNode(request.responseText)
para.appendChild(txt)
document.getElementById('new').appendChild(para)
}
}

request.send(null)
ajax({
url: './TestXHR.aspx', // 请求地址
type: 'POST', // 请求方式
data: { name: 'super', age: 20 }, // 请求参数
dataType: 'json',
success(response, xml) {
// 此处放成功后执行的代码
},
fail(status) {
// 此处放失败后执行的代码
},
})

function ajax(options) {
options = options || {}
options.type = (options.type || 'GET').toUpperCase()
options.dataType = options.dataType || 'json'
const params = formatParams(options.data)
let xhr

// 创建 - 非IE6 - 第一步
if (window.XMLHttpRequest) {
xhr = new XMLHttpRequest()
} else {
// IE6及其以下版本浏览器
xhr = new ActiveXObject('Microsoft.XMLHTTP')
}

// 接收 - 第三步
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
const status = xhr.status
if (status >= 200 && status < 300)
options.success && options.success(xhr.responseText, xhr.responseXML)
else
options.fail && options.fail(status)
}
}

// 连接 和 发送 - 第二步
if (options.type === 'GET') {
xhr.open('GET', `${options.url}?${params}`, true)
xhr.send(null)
} else if (options.type === 'POST') {
xhr.open('POST', options.url, true)
// 设置表单提交时的内容类型
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
xhr.send(params)
}
}

// 格式化参数
function formatParams(data) {
const arr = []

for (const name in data)
arr.push(`${encodeURIComponent(name)}=${encodeURIComponent(data[name])}`)

arr.push(`v=${Math.random()}`.replace('.', ''))
return arr.join('&')
}
function getJSON(url) {
return new Promise((resolve, reject) => {
const request = new XMLHttpRequest()

request.open('GET', url)

request.onload = function () {
try {
if (this.status === 200)
resolve(JSON.parse(this.response))
else
reject(new Error(`${this.status} ${this.statusText}`))
} catch (e) {
reject(e.message)
}
}

request.onerror = function () {
reject(new Error(`${this.status} ${this.statusText}`))
}

request.send()
})
}

getJSON('data/sample.json')
.then((ninjas) => {
assert(ninjas !== null, 'Get data')
})
.catch(e => handleError(`Error: ${e}`))

Data Format

FormatSize (bytes)Download (ms)Parse (ms)
Verbose XML582,960999.4343.1
Verbose JSON-P487,913598.20.0
Simple XML437,960475.183.1
Verbose JSON487,895527.726.7
Simple JSON392,895498.729.0
Simple JSON-P392,913454.03.1
Array JSON292,895305.418.6
Array JSON-P292,912316.03.4
Custom Format (script insertion)222,91266.311.7
Custom Format (XHR)222,89263.114.5

Cross Origin Request

<!-- HTML -->
<meta http-equiv="Access-Control-Allow-Origin" content="*" />
Response.Headers.Add('Access-Control-Allow-Origin', '*')
$.ajax({
url: 'http://map.oicqzone.com/gpsApi.php?lat=22.502412986242&lng=113.93832783228',
type: 'GET',
dataType: 'JSONP', // 处理 AJAX 跨域问题.
success(data) {
$('body').append(`Name: ${data}`)
},
})

RESTful

  • Client/Server architecture.
  • Stateless.
  • Cacheable.
  • Layer system.
  • Code via need.
  • Isomorphic interface.
  • Design reference.

Server-Sent Events

  • Event source API.
  • Server-sent events API.
const source = new EventSource('/path/to/stream-url')

source.onopen = function () {}

source.onerror = function () {}

source.addEventListener('foo', (event) => {
processFoo(event.data)
})

source.addEventListener('ping', (event) => {
processPing(JSON.parse(event.data).time)
})

source.onmessage = function (event) {
log(event.id, event.data)
if (event.id === 'CLOSE')
source.close()
}