Efficient client-server interactions make or break a web application. This talk as about advanced techniques, which can be used with popular frameworks, to improve performance, and simplify data manipulations.
8. HTTP response
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 18193
Date: Sun, 30 Apr 2017 23:52:36 GMT
...body, if needed...
9. Design decisions I
Return Promise.
Helps with composability.
A way from the callback hell.
Pack all request parameters together.
Use raw {} for simplicity.
We can manipulate it.
10. Design decisions II
Be smart about response
Users want to deal in terms of data:
Send data
Receive data
In most cases a response object is not
needed.
11. Design decisions III
Be smart about response
Sometimes we need a response:
To read status and headers.
Cater for most common cases:
JSON in/out should be simple.
The rest should be possible.
12. Design decisions IV
We interpret status code.
4XX and 5XX are bad statuses.
204 is good, but no response.
⇒ undefined.
13. Design decisions V
We interpret MIME.
If you don’t set:
Content-Type
Accept-Type
⇒ application/json
20. How we interpret result?
We know Content-Type.
application/json ⇒ JSON.
application/xml ⇒ XML.
MIME types are covered by RFCs.
We can map them to whatever.
Or return a string or a buffer.
21. Design: helpers I
We can provide helpers for verbs:
io.get (url, queryData);
io.head (url, queryData);
io.post (url, data);
io.put (url, data);
io.patch (url, data);
io.delete (url, data);
22. Design: helpers II
url is a string, or an object like for io().
queryData and data are optional.
A helper overrides a verb.
23. Design: helpers III
// url as an object? for REST:
var opts = {url: url, headers: headers};
io.get(opts);
io.put(opts, data);
io.patch(opts, data);
io.delete(opts);
29. Let’s POST JSON with XHR I
var xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
// set up headers
xhr.setRequestHeader('Content-Type',
'application/json');
xhr.setRequestHeader('Accept-Type',
'application/json');
// continues on the next slide
30. Let’s POST JSON with XHR II
// optional
xhr.overrideMimeType(
'application/json');
// set up callbacks
xhr.onload = function (e) {};
xhr.onerror = function (e) {};
xhr.ontimeout = function (e) {};
// continues on the next slide
31. Let’s POST JSON with XHR III
// optional
xhr.onprogress = function (e) {};
xhr.upload.onprogress =
function (e) {};
// finally
xhr.send(JSON.stringify(data));
32. XHR: summary
Three slides of boilerplate.
Much more with actual code.
Callbacks are not composable.
A lot of repetitive code.
Realistically requires a wrapper.
33. JSONP: history
Formulated by Bob Ippolito.
Published in December 2005.
Uses existing facilities: <script>
Works in all browsers.
Works cross-origin.
35. December 2005
First Narnia came out.
SNL’s “Lazy Sunday” on selecting “the
dopest route” to see it:
– I prefer MapQuest!
– That’s a good one too!
– Google Maps is the best!
– True dat! Double true!
36. Let’s POST JSON with JSONP
We can’t. Limitations:
Only GET verb.
Only query parameters.
Only JSON as a returned value.
37. Let’s GET JSON with JSONP I
var script =
document.createElement('script');
script.onerror = function (e) {};
window.unique = function (data) {
delete window.unique;
script.parentNode.removeChild(script);
// do something with data
};
// continues on the next slide
38. Let’s GET JSON with JSONP II
// now we run it
script.src = url +
/* our parameters */ '?a=1' +
'&callback=' +
encodeURIComponent('unique');
document.documentElement.
appendChild(script);
39. JSONP: summary
Let’s face it: it is a hack.
Callbacks are not composable.
Repetitive code to encode parameters.
Realistically requires a wrapper.
40. Fetch: history
Started by WHATWG.
Status (April 2017):
Not available in IE11.
Available everywhere else.
Reasonable poly ll is available.
42. Fetch: more details
Simpli es Service Workers.
Currently can’t be canceled.
Supports streaming.
De nes numerous classes.
Can be heavy when poly lled.
43. Fetch: even more details
No cookies are sent by default.
CORS is disabled by default.
Any response from a server is success.
Even 4XX and 5XX ones.
44. Let’s POST JSON with Fetch I
The example is adapted from Shahal
Talmi’s article.
Angular core contributor.
var opts = {
method: 'POST',
mode: 'cors',
credentials: 'include' // cookies!
};
// continues on the next slide
45. Let’s POST JSON with Fetch II
// continue filling in opts
opts.headers = {
'Content-Type': 'application/json',
'Accept-Type': 'application/json'
};
opts.body = JSON.stringify(data);
// continues on the next slide
46. Let’s POST JSON with Fetch III
fetch(url, opts).then(response =>
response.ok ? response.json() :
Promise.reject(response.status)
)
// user's code for then() & catch()
47. Fetch: summary
Streaming is nice to have, but it is a rare
edge case.
Still a lot of repetitive code.
Due to selected defaults.
Realistically requires a wrapper.
64. heya/io II
Can be used with AMD and globals.
Works with native Promise or any
Promise-like object.
Can be used with heya/async.
Can cancel() I/O.
Supports progress.
65. POST JSON with heya/io
io.post(url, data).then(result => {
console.log(result);
}).catch(res => {
if (res instanceof io.BadStatus) {
console.log(res.xhr.status);
} else {
console.log(res);
}
});
67. Orchestrating I/O
I/O is much more than just a transport.
Caching is a big topic.
App-level cache.
Cache busting.
I/O testing is a must.
68. Cache issues
In HTTP world cache:
Controlled by a server.
Server cannot recall a response.
Client cannot evict a response.
69. App-level cache
App frequently knows:
When an object is modi ed.
Dependencies between objects.
We need an app-level cache.
The alternative: no HTTP cache.
70. Cache issues: servers
Server cache headers are frequently
miscon gured.
Evidence: cache-busting.
/url?bust=123456789
bust uses a random payload, so all
requests are unique.
71. heya/io is extendable I
The I/O pipeline is well-de ned.
All stages can be extended:
On per-request basis.
On per-app basis.
72. heya/io is extendable II
Pipeline extensions: services.
XHR replacements: transports.
All extensions should be included
explicitly.
Pay only for what you use.
74. heya/io: bust service I
var req = io.get({
url: 'abc',
bust: true
});
// abc?io-bust=1470185125354-507943
75. heya/io: bust service II
var req = io.get({
url: 'abc',
bust: 'xyz'
});
// abc?xyz=1470185125354-507943
76. heya/io: bust service III
By default: no bust.
Con gurable:
Bust key.
Bust value generating.
77. heya.io: cache service I
Storage-based:
Session storage (default).
Local storage (permanent).
Caches GET requests automatically.
To opt out:
cache: false
78. heya.io: cache service II
Main API (rarely used directly):
io.cache.save('/abc', {a: 1});
io.cache.remove('/abc');
Direct access to the underlying storage
object.
79. heya/io: mock service I
Simple way to intercept, replace, or
transform an I/O request.
A must for testing!
Trivial redirects.
Rapid prototyping.
80. heya/io: mock service II
// canned data for exact url
io.mock('/abc', () => 42);
io.get('/abc').then(data => {
console.log(data); // 42
});
81. heya/io: mock service III
// canned data for url prefix
io.mock('/abc*', () => 42);
io.get('/abc/1').then(data => {
console.log(data); // 42
});
82. heya/io: mock service IV
// redirect
io.mock('/abc', () => io.get('/xyz'));
io.get('/abc').then(data => {
console.log(data); // from /xyz
});
83. heya/io: mock service V
// timeout (uses heya/async)
io.mock('/abc', () =>
timeout.resolve(500).then(() => 42));
io.get('/abc').then(data => {
console.log(data); // 42 after 0.5s
});
84. heya/io: mock service VI
// timeout (uses setTimeout())
io.mock('/abc', () =>
new Promise(resolve => {
setTimeout(function () {
resolve(42);
}, 500);
}));
io.get('/abc').then(data => {
console.log(data); // 42 after 0.5s
});
85. heya/io: mock service VII
// cascaded calls
io.mock('/abc',
() => io.get('/a').then(
value => io.get('/b', {q: value.x})
).then(
value => io.get('/c', {q: value.y})
)
);
86. heya/io: mock service VIII
// server error
io.mock('/abc',
() => io.mock.makeXHR({status: 500})
);
88. heya/io: bundle service II
Problems with the tradition:
We may exceed number of HTTP
connections.
Potential stalling.
89. heya/io: bundle service III
Problems with the tradition:
Each payload is small, and
compressed separately.
Poor compression.
90. heya/io: bundle service IV
Bundle I/O
Client Server
HTTPconnections
HTTPconnections
bundle bundle
91. heya/io: bundle service V
Bundle’s narrative:
Client collects I/O requests.
Bundles them in one request.
Sends it to a well-known URL.
92. heya/io: bundle service VI
Bundle’s narrative:
Server acts as a proxy.
Runs all requests in parallel locally.
Sends back collected responses.
93. heya/io: bundle service VII
Bundle’s narrative:
Client unbundles responses.
Noti es requesters.
94. heya/io: bundle service VIII
Important points:
Bundling works transparently.
No code modi cations!
Usually GETs are bundled.
To opt out:
bundle: false
95. heya/io: bundle service IX
Assumes a server handler.
Reference: heya/bundler.
Local server connections are fast and low-
lag.
96. heya/io: bundle service X
Bundling helps with compression.
Bundling requires just one HTTP
connection.
97. heya/io: bundle service XI
HTTP/2 alleviates some problems.
Bundle as fast as the slowest request.
In a real app the speed gain was up to
30%.
98. heya/io: bundle service XII
Bundler can return responses for
unrequested requests.
Similar to HTTP/2 Server Push.
Cache will be populated.
99. heya/io: bundle service XIII
Behind the scenes:
Client collects requests.
Sends the array as JSON.
Server unpacks.
Runs them in parallel.
100. heya/io: bundle service XIV
Behind the scenes:
Server collects responses.
Including errors.
Sends the array as JSON back.
Client unpacks, saves to cache.
105. heya/io: prefetch V
It was 3.69s, now it is 3.22s.
We saved 470ms — whoopee do!
It was under ideal conditions.
Really fast 12 core 64G RAM rig.
How about mobile users?
108. heya/io: prefetch VII
It was 16.88s, now it is 15.36s.
We saved 1.52s!
We deducted almost all /api.
109. heya/io: prefetch VIII
bundle service has provisions for
prefetching.
It can be done transparently!
See “Cookbook: bundle” in Wiki of
https://github.com/heya/io