fetch()
const response = await fetch(url, options) // resolves with response headers
const result = await response.json()
does network requests from JavaScript w/o options, it is a GET request, downloading the contents of the url fetch resolves with an object of the built-in Response class as soon as the server responds with headers At this stage we can check HTTP status, to see whether it is successful or not, check headers, but don’t have the body yet Abnormal HTTP-statuses, such as 404 or 500 do not cause an error to get the response body, we need to use an additional method call
async function fetchPage() {
const response = await fetch('/posts/fetch')
const result = await response.text()
console.log(result)
}
const FetchPage = () => <button onClick={fetchPage}>fetch this page content and put into console</button>
<FetchPage />
Without await
fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits')
.then(response => response.json())
.then(commits => alert(commits[0].author.login))
Response response.status HTTP status code, e.g. 200 response.ok boolean, true if the HTTP status code is 200-299 response.body ReadableStream object, it allows you to read the body chunk-by-chunk response.text() read the response and return as text response.json() parse the response as JSON can choose only one body-reading method response.formData() return the response as FormData object response.blob() return the response as Blob (binary data with type) response.arrayBuffer() return the response as ArrayBuffer (low-level representation of binary data)
const url = 'https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits'
const response = await fetch(url)
if (!response.ok) throw new Error("HTTP-Error: " + response.status) // if HTTP-status is 200-299
const json = await response.json() // read response body and parse as JSON
console.log(json[0].author.login)
Show image in Binary
const url = 'https://i.insider.com/5ec6a5772618b94ebe726e95'
const response = await fetch(url)
console.log(response)
let blob = await response.blob() // download as Blob object
let img = document.createElement('img')
img.style = 'position:fixed; top:50px; left:50px; width:300px'
document.body.append(img)
img.src = URL.createObjectURL(blob) // show it
One body-reading method
let text = await response.text(); // response body consumed
let parsed = await response.json(); // fails (already consumed)
response.headers response headers are available in a Map-like headers object in response.headers it’s not exactly a Map, but it has similar methods
let response = await fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits');
// get one header
console.log(response.headers.get('Content-Type')) // application/json; charset=utf-8
// iterate over all headers
for (let [key, value] of response.headers) {
console.log(`${key} = ${value}`)
}
request.headers To set a request header in fetch, we can use the headers option It has an object with outgoing headers, like this
let response = fetch(protectedUrl, {
headers: {
Authentication: 'secret'
}
})
List of forbidden HTTP headers which we can not set: Accept-Charset , Accept-Encoding , Access-Control-Request-Headers , Access-Control-Request-Method , Connection , Content-Length , Cookie , Cookie2 , Date , DNT , Expect , Host , Keep-Alive , Origin , Referer , TE , Trailer , Transfer-Encoding , Upgrade , Via , Proxy-* , Sec-* POST request To make a POST request, or a request with another method, we need to use fetch options Content-Type is usually... 'Content-Type': 'text/plain;charset=UTF-8' for string request body, default 'Content-Type': 'application/json;charset=utf-8' for json request body 'Content-Type': 'multipart/form-data' for form data
let user = { name: 'John', surname: 'Smith' }
let response = await fetch('/article/fetch/post/user', {
method: 'POST',
body: JSON.stringify(user),
headers: {
'Content-Type': 'application/json;charset=utf-8'
},
})
let result = await response.json()
console.log(result.message)
Request body can be... a string (e.g. JSON-encoded), used most of the times FormData object, to submit the data as form/multipart Blob/BufferSource to send binary data URLSearchParams , to submit the data in x-www-form-urlencoded encoding, rarely used Send an image as blob can submit binary data with fetch using Blob or BufferSource objects there’s a <canvas> where we can draw by moving a mouse over it note, we don’t set Content-Type header manually, because a Blob object has a built-in type image/png , as generated by toBlob For Blob objects that type becomes the value of Content-Type
<body style="margin:0">
<canvas id="canvasElem" width="100" height="80" style="border:1px solid"></canvas>
<input type="button" value="Submit" onclick="submit()">
canvasElem.onmousemove = function(e) {
let ctx = canvasElem.getContext('2d')
ctx.lineTo(e.clientX, e.clientY)
ctx.stroke()
}
async function submit() {
let blob = await new Promise(resolve => canvasElem.toBlob(resolve, 'image/png'))
let response = await fetch('/article/fetch/post/image', {
method: 'POST',
body: blob
})
// the server responds with confirmation and the image size
let result = await response.json()
console.log(result.message)
}
FormData it is an object to represent HTML form data, which can be sent with fetch as a body from the server point of view, that looks like a usual form submission if HTML form element is provided, it automatically captures its fields
<form id="formElem">
<input type="text" name="name" value="John">
<input type="text" name="surname" value="Smith">
<input type="submit">
</form>
const formData = new FormData([form])
formElem.onsubmit = async (e) => {
e.preventDefault()
const response = await fetch('/article/formdata/post/user', {
method: 'POST',
body: new FormData(formElem)
})
const result = await response.json()
console.log(result.message) // User saved
}
FormData methods formData.append(name, value) add a form field with the given name and value formData.append(name, blob, fileName) add a field as if it were <input type="file"> , the third argument fileName sets file name (not form field name), as it were a name of the file in user’s filesystem formData.delete(name) remove the field with the given name formData.get(name) get the value of the field with the given name formData.has(name) if there exists a field with the given name, returns true , otherwise false formData.set(name, value) difference is that .set removes all fields with the given name, and then appends a new field, it makes sure there’s only one field with such name formData.set(name, blob, fileName) same
for(let [name, value] of formData) {
console.log(`${name} = ${value}`)
// key1 = value1
// key2 = value2
}
formData.forEach((x, y, z) => console.log(x,y,z))
// value1 key1 FormData{}
// value2 key2 FormData{}
File in a form The form is always sent as 'Content-Type': 'multipart/form-data' this encoding allows to send files
<form id="formElem">
<input type="text" name="firstName" value="John">
Picture: <input type="file" name="picture" accept="image/*">
<input type="submit">
</form>
formElem.onsubmit = async (e) => {
e.preventDefault()
const response = await fetch('/article/formdata/post/user-avatar', {
method: 'POST',
body: new FormData(formElem)
})
const result = await response.json()
alert(result.message) // User with picture, firstName: John, picture size:185123
}
Blob in a form convenient to send an image as a part of the form, with additional fields, such as “name” and other metadata example submits an image from <canvas> , along with some other fields, as a form, using FormData
<body style="margin:0">
<canvas id="canvasElem" width="100" height="80" style="border:1px solid"></canvas>
<input type="button" value="Submit" onclick="submit()">
</body>
canvasElem.onmousemove = function(e) {
let ctx = canvasElem.getContext('2d')
ctx.lineTo(e.clientX, e.clientY)
ctx.stroke()
}
async function submit() {
let imageBlob = await new Promise(resolve => canvasElem.toBlob(resolve, 'image/png'))
let formData = new FormData()
formData.append("firstName", "John")
formData.append("image", imageBlob, "image.png")
let response = await fetch('/article/formdata/post/image-form', {
method: 'POST',
body: formData
})
let result = await response.json()
alert(result.message)
}
Download progress response.body is an object of ReadableStream class and gives full control over the reading process it provides body chunk-by-chunk, as it comes no way to track upload progress instead of response.json() or other methods we obtain a reader we can’t use both these methods to read the same response then we get total length, it may be absent for cross-origin requests, but usually it’s at place then we read the data and add body chunks into the array done is true for the last chunk value is Uint8Array of the chunk bytes after the response is consumed, we won’t be able to “re-read” it using response.json() or another way then concatenate chunks into single Uint8Array then we need to create a string from chunksAll byte array finally parse it into JSON
// Step 1: start the fetch
const response = await fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits?per_page=100')
const reader = response.body.getReader()
// Step 2: get total length
const contentLength = +response.headers.get('Content-Length')
// Step 3: read the data
let receivedLength = 0 // received that many bytes at the moment
let chunks = [] // array of received binary chunks (comprises the body)
while(true) {
const {done, value} = await reader.read()
if (done) break
chunks.push(value)
receivedLength += value.length
console.log(`Received ${receivedLength} of ${contentLength}`)
}
// Step 4: concatenate chunks into single Uint8Array
let chunksAll = new Uint8Array(receivedLength)
let position = 0
for(let chunk of chunks) {
chunksAll.set(chunk, position)
position += chunk.length
}
// Step 5: decode into a string
let result = new TextDecoder("utf-8").decode(chunksAll)
// We're done!
let commits = JSON.parse(result)
console.log(commits[0].author.login)
Binary content instead of a string... just replace steps 4 and 5 with a single line that creates a Blob from chunks.
let blob = new Blob(chunks)
Abort object fetch returns a promise, no concept to abort it there is a special object new AbortController() that allows to abort asynchronous tasks const controller = new AbortController() controller.abort() abort! controller.signal emits the "abort" event controller.signal.aborted property becomes true The party that performs a cancelable operation gets the "signal" object and sets the listener to trigger when controller.abort() is called.
const signal = controller.signal
signal.addEventListener('abort', () => alert("abort!"))
Abort fetch request to cancel fetch, pass the signal property of an AbortController as a fetch option fetch will listen to abort events on signal when a fetch is aborted, its promise rejects with an error AbortError so we should handle it, e.g. in try..catch AbortController allows to cancel multiple fetches at once
let controller = new AbortController()
// abort in 1 second
setTimeout(() => controller.abort(), 1000)
try {
let response = await fetch('/article/fetch-abort/demo/hang', {
signal: controller.signal
})
} catch(err) {
if (err.name == 'AbortError') { // handle abort()
alert("Aborted!")
} else {
throw err
}
}
Abort multiple fetches...
const urls = ['url1', 'url2', 'url3']
const controller = new AbortController()
const fetchJobs = urls.map(url => fetch(url, {
signal: controller.signal
}))
const results = await Promise.all(fetchJobs)
controller.abort() // if called from anywhere, it aborts all fetches
Abort asynchronous task together with fetches...
const urls = ['url1', 'url2', 'urlN']
const controller = new AbortController()
const ourJob = new Promise((resolve, reject) => {
//...
controller.signal.addEventListener('abort', reject)
})
const fetchJobs = urls.map(url => fetch(url, {
signal: controller.signal
}))
const results = await Promise.all([...fetchJobs, ourJob])
Fetch API https://javascript.info/fetch Most of these options are used rarely
const promise = fetch(url, {
method: "GET", // POST, PUT, DELETE, etc
headers: {
// the content type header value is usually auto-set depending on the request body
"Content-Type": "text/plain;charset=UTF-8"
},
body: undefined, // string, FormData, Blob, BufferSource, or URLSearchParams
referrer: "about:client", // or "" to send no Referer header, or an url from the current origin
referrerPolicy: "no-referrer-when-downgrade", // no-referrer, origin, same-origin...
// This option may be useful when the URL for fetch comes from a 3rd-party
mode: "cors", // same-origin, no-cors
// whether fetch should send cookies and HTTP-Authorization headers with the request
credentials: "same-origin", // omit, include
// The cache options allows to ignore HTTP-cache or fine-tune its usage
cache: "default", // no-store, reload, no-cache, force-cache, or only-if-cached
// Normally, fetch transparently follows HTTP-redirects, like 301, 302 etc.
// The redirect option allows to change that: "follow" – the default, follow HTTP-redirects, "error" – error in case of HTTP-redirect, "manual" – allows to process HTTP-redirects manually.
redirect: "follow", // manual, error
// The integrity option allows to check if the response matches the known-ahead checksum.
// Then fetch will calculate SHA-256 on its own and compare it with our string
// In case of a mismatch, an error is triggered.
integrity: "", // a hash, like "sha256-abcdef1234567890"
// We can use the window.onunload event normally associated network requests are aborted
// keepalive option tells the browser to perform the request in the background, even after it leaves the page
keepalive: false, // true
signal: undefined, // AbortController to abort request
window: window // null
})
Long polling simplest way of having persistent connection with server no need to periodically request info, which is good performance wise no delay in messaging a request is sent from browser to the server connection hangs, the server doesn’t close the connection until it has a message to send when a message appears the server responds to the request with it connection is closed the browser makes a new request immediately and so on... long polling works great in situations when messages are rare subscribe() function makes a fetch, then waits for the response, handles it and calls itself again server should be ok with many pending connections
async function subscribe() {
let response = await fetch("/subscribe")
if (response.status == 502) {
// Status 502 is a connection timeout error,
// may happen when the connection was pending for too long,
// and the remote server or a proxy closed it
// let's reconnect
await subscribe()
} else if (response.status != 200) {
// An error - let's show it
showMessage(response.statusText)
// Reconnect in one second
await new Promise(resolve => setTimeout(resolve, 1000));
await subscribe()
} else {
// Get and show the message
let message = await response.text()
showMessage(message)
// Call subscribe() again to get the next message
await subscribe()
}
}
subscribe()
Fetch not catching some errors For ex. it does not throw an 403 error and you need to manually check the response. Axios just throws an error.