Async programming & network request in JavaScript
async actions: init now but finish later (code below it won’t wait) ..
Callback
loadScript('xxx.js', (error, script) => { .. })
(“error-1st” cb style) ..: cb hell / pyramid of doom
Fn Call Scheduling: setTimeout/setInterval(..)
.. (enqueue in macrotask ..)
1 | let timerId = setTimeout/setInterval(fn, delay=0, ...args); // keep fn-ref in scheduler (not GC) |
- setTimeout(): set
this=window
.. - setInterval(): fn-exe-time > delay? …
- nested setTimeout vs setInterval ..
- tasks: Output every second
Promise
new Promise((resolve, reject) => .. ).then(value => .. ).catch(err => .. ).finally(fn)
..
- error occur in setTimeout (except
reject(err)
) not catched .. - unhandlerd rejection & window
unhandledrejection
event .. - cb vs promise: promise only single result (cb can be called mtp times) ..
async/await
..
async
bf fn: return a promise & enableawait
await
&try-catch
— promise.then() & catch()
Promise API
let promise = Promise.all/allSettled/race(...promises);
..Promise.all()
: all resolved or rejectPromise.allSettled()
:[...{ status: 'fulfilled/rejected', value/reason: xxx }]
Promise.race()
: 1st settled promise.result
Promise.resolve(value)/reject(err)
: a resolved / rejected promise, same asnew Promise((resolve, reject) => resolve(value) || reject(error))
Abort async task: AbortController
- when call
ctrller.abort()
called ..ctrller.signal
emitabort
event (ctrller.signal.aborted
= true)ctrller.signal.addEL('abort', handler)
- abort fetch op:
fetch(url, { signal: ctrller.signal })
..- when
ctrller.abort()
called, fetch get event fromsignal
then aborted & promise rejected withAbortError
- when
Network request
AJAX: load new info without page reload
fetch & XHR
network method fetch(url, [request-options])
.. — fetch API
2-stage response
await fetch(..): response
: server response with headers (returned promise resolve with obj of Response) ..response.status
,response.ok
(if HTTP-status 200-299 — 404 / 500 do not cause promise reject)response.headers
: map-like obj ..
- get response body:
response.json()/blob() ..
(asyn) …
progress track
- download:
response.body.getReader().read(): {done, value}
&+response.headers.get('Content-Length')
..response.body
:ReadableStream
, repeatedly call read() to read body chunk-by-chunk & concatvalue.length
- upload:
xhr.upload.onprocess
&e.loaded / e.total
..xhr.upload.onprocess
: trigger when data sent .. — resume file upload algorithmxhr.onprogress
: trigger when download response .. (a data packet of the response arrived ..)
Task: getGithubUsers(names)
CORS
- origin: “protocol://domain:port” .., .. & same origin policy: only allow to set
location
.. & get ref ofiframe.contentWindow
..- make same 2nd-level domain wins same origin:
document.domain = 'site.com'
..
- make same 2nd-level domain wins same origin:
- cross-win msg (from any origin) — win
postMessage(..)
interface ..- sender-win call
receiverWin.postMessage(..)
— triggerreceiverWin.message
event in receiver-win - note: add handler to
receiverWin.message
event byaddEventHandler()
..
- sender-win call
- the old ways to work around cors:
<form>
&<script>
…<form target="iframe" ..>
: open action.url in ifame- script & JSONP protocol:
<script src="another-site.com?callback=xxx">
- remote dynamically generates a script, load and exe & call our local fn
xxx(data)
- remote dynamically generates a script, load and exe & call our local fn
Fetch & CORS
Cross-origin requests & special response headers …
- simple request: browser send header
Origin
—Access-Control-Allow-Origin
… - non-simple request: browser extra “preflight” request (ask for permission 1st)
- method
OPTIONS
+A-C-Request-Method
/*-Headers
—A-C-A-Origin
/*-Methods
/*-Headers
…
- method
- js default accessible response headers —
Access-Control-Expose-Headers
… - request with credentials …, ..
- request header:
Origin
vsReferer
… — fetchreferrer, referrerPolicy
option … - http code 301, 302 & fetch option
redirect
… - window.onunload & fetch option
keepalive
…
B/S communication: Polling & WebSocket
Get msg from server
- Regular polling: msg pass delay, server bombed with requests
- Long polling: request (e.g. fetch) → server keep conn open until response with data → response process & re-request
- good when msgs rare ..
- timeout status 502
- Server Sent Events
EventSource
— vsWebSocket
: ES support auto-reconnnew EventSource(url, ..)
: start conn .. —open
(connected) /error
event (response wrongContent-Type
/ HTTP status (e.g. 500 ..) ..)- server send data (response 200 &
Content-Type: text/..
to write text msg) ..message
event:es.onmessage(e.data)
- custom event:
es.addEL(..)
(data come withevent
field) ..
- browser close conn:
es.close()
.. - no reconn: response 204 |
error
event ..- check if reconn when error:
es.onerror
es.readyState === EventSource.CONNECTING
..
- check if reconn when error:
- response msg fields:
retry
(for timeout),event
(for custom events),data
,id
(for reconn) …
Data exchange: WebSocket: persistent conn, no cors limitation ..
new WebSocket("wss://xxx ", ..)
: start conn .. —open
event: connectedwss://
protocol (WebSocket over TLS) ..
- data exchange:
ws.send(data)
&ws.onmessage(e.data)
.. - conn close:
ws.close(code=1000, reason);
&ws.onclose
:e.code/reason/wasClean
…
Storing data in the browser
Cookie
Cookie: name=value;
pairs stored in browser ..
- part of HTTP protocol ..
- request to server & server set a corresponding cookie using response
Set-Cookie
HTTP-header - subsequent request to the same domain, browser auto add them (in
Cookie
HTTP-header) , e.g. in auth
- request to server & server set a corresponding cookie using response
- read/write by
document.cookie
.., .. - cookie options …
- by default, cookie rmed af browser close (session cookie) —
expires
/max-age
.. secure
: only accessible over HTTPS (if set inhttps://
) ..samesite
: prevent XSRF attack — not sent cookie if request comes from outside ..- only work for browser af 2017, ignored by old browsers — use with the old solution
- XSRF attack: user submit a form to
bank.com
inevil.com
& browser auto-sent auth-cookie - old solution: forms generated by
bank.com
have a special “XSRF protection token” & when receive form,bank.com
checks for such token
- by default, cookie rmed af browser close (session cookie) —
- 3rd-party cookies: set by domain other than the page visiting ..
- the cookie bound to the domain, can use to track user when moves btn sites
- GDPR enforce explicit user permission to set it
Web storage obj: local/sessionStorage
save key/value pairs (str-only ..) in browser by web storage obj ..
localStorage
: data shared btn all wins with the same origin & not expire ..sessionStorage
: only current browser tab (even page refresh, but not re-open) ..- browser storage obj vs cookie: not sent to server, so larger storage (up to 5mb ..) ..
window.onstorage
event: triggers when data updated inlocal/sessionStorage
…- triggers on all win-obj where the storage accessible, except the causing one — allow same origin wins to exchange msgs
- task: Autosave a form field
indexedDB
indexedDB: mtp types of key/value, bigger storage (intended for offline apps), support transaction …
let openRequest = indexedDB.open(DBNname: str, schemaVersionNo: int);
: conn to a DB, bound to current origin- openRequest obj events:
error
&console.error("Error", openRequest.error)
success
&let db = openRequest.result
upgradeneeded
: when client local db versione.oldVersion
< argschemaVersionNo
e.newVersion
- e.g. user come 1st time (db not exist yet, version = 0),
- or client load old code (prevent by HTTP caching headers, or prompt reload page)
- handler finishes without errs => trigger
openRequest.onsuccess
- Parallel update problem: both old/new db conn exists (in diff tabs)
versionchange
event trigger in tab1 (old version db conn): close old db conn & reload page (seedb.onversionchange
)- if old db conn not close,
open()
in tab2 triggerblock
event (ask tab2 user to close other tabs for update) - variants ..
- delete db by
let deleteRequest = indexedDB.deleteDatabase(name)
- check
deleteRequest.onsuccess/onerror
for deletion result
- check
- object store (i.e. table in RDB)
db.create/deleteObjectStore(..), objectStoreNames.contains(..)
..- created/removed/altered obj-store only in
upgradeneeded
handler (auto-generate aversionchange
transaction) - can make data-ops outside
upgradeneeded
handler - obj-store CRUD requests
- search by key:
IDBKeyRange
obj &store.get*([query])
(values sorted by key order) ..
by other fields byindex
(return a obj-store-liked obj keyed by that field, could mtp-value) .. - add/update & delete data request:
store.add/put(obj)
,store.delete(..)/clear()
- search by key:
- iter obj-store by
cursor
..
- transaction
- starts t & get it obj-store:
let t = db.transaction(..)
let books = t.objectStore("books")
& other request cmds request.onsuccess/error
&request.result/error
- all t.requests finish => microtasks queue empty => auto-commit =>
t.oncomplete
event- async ops/macrotask not allowed btn t.requests: t closed bf macrotasks starts =>
TransactionInactiveError
- solution: split t ops & macrotasks/async ops apart ..
- async ops/macrotask not allowed btn t.requests: t closed bf macrotasks starts =>
t.abort()
&t.onabort
event,t.error
- failed request auto abort t, can be prevent in
request.onerror
handler bye.preventDefault()
- failed request auto abort t, can be prevent in
- starts t & get it obj-store:
- IndexedDB events bubble:
request
→transaction
→database
db.onerror = function(event) { let request = event.target.. }
- Promise wrapper &
try..catch
vs Addingonsuccess/onerror
to every request - demo in summary ..