O slideshow foi denunciado.
Utilizamos seu perfil e dados de atividades no LinkedIn para personalizar e exibir anúncios mais relevantes. Altere suas preferências de anúncios quando desejar.

Advanced I/O in browser

253 visualizações

Publicada em

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.

Publicada em: Tecnologia
  • Seja o primeiro a comentar

  • Seja a primeira pessoa a gostar disto

Advanced I/O in browser

  1. 1. Advanced I/O in browser Eugene Lazutkin, 5/2/2017, ClubAjax, Dallas, TX @uhop, www.lazutkin.com
  2. 2. Outline Prototype an I/O library. What we have, and what is wrong with it. Libraries! What do Angular and React do? Back to the board! Advanced I/O
  3. 3. Browser Now provides all low-level stuff. We can (without jQuery!): Search by selector. Calculate geometry. Manipulate CSS classes. Much more!
  4. 4. How about I/O? Stone age. We still need to use libraries. Let’s design an ideal solution. Then compare it with what we have.
  5. 5. The ideal
  6. 6. HTTP Simple text format. The same is used by HTTPS. Covered by standards. Uses verbs and status codes. Consists of headers and body.
  7. 7. HTTP request POST /api HTTP/1.1 Host: localhost:8000 Connection: keep-alive User-Agent: ...user agent string... Content-Type: application/json Accept-Encoding: gzip, deflate Cookie: io=JSwSxqG0tIMQ_ZtZAABl ...body, if needed...
  8. 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. 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. 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. 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. 12. Design decisions IV We interpret status code. 4XX and 5XX are bad statuses. 204 is good, but no response. ⇒ undefined.
  13. 13. Design decisions V We interpret MIME. If you don’t set: Content-Type Accept-Type ⇒ application/json
  14. 14. Design decisions VI Recognize special data: FormData — forms. Document — XML. ArrayBuffer, Blob.
  15. 15. Design decisions VII Form query: Bad: url: '/abc/?name=' + name + '&age=' + age Good: url: '/abc', query: {name: name, age: age}
  16. 16. XKCD: Exploits of a Mom https://xkcd.com/327/ Don’t be a Bobby Tables!
  17. 17. Design decisions VIII Different types for different errors. Use instanceof to make a choice. Errors: wrong request, timeout, bad status.
  18. 18. Result io({ url: url, method: 'POST', headers: { 'X-Custom-Header': 'abc' }, data: data // JSON }) // then() and catch()
  19. 19. Result: POST JSON example io(req).then(result => { console.log(result.name); }).catch(res => { if(res instanceof io.FailedIO) {} if(res instanceof io.TimedOut) {} if(res instanceof io.BadStatus) { console.log(res.xhr.status); } });
  20. 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. 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. 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. 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);
  24. 24. POST JSON reformulated io.post(url, data). then(result => ...). catch(res => ...); Sounds simple, eh?
  25. 25. What we have
  26. 26. XHR: history The venerable XMLHttpRequest. Implemented by Microsoft. For Outlook Web Access. Shipped in March 1999 in IE5. As an ActiveX.
  27. 27. Oh, 1999!
  28. 28. March 1999 The Matrix was released. It blew my mind! You go, bros sisters!
  29. 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. 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. 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. 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. 33. JSONP: history Formulated by Bob Ippolito. Published in December 2005. Uses existing facilities: <script> Works in all browsers. Works cross-origin.
  34. 34. Oh, 2005!
  35. 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. 36. Let’s POST JSON with JSONP We can’t. Limitations: Only GET verb. Only query parameters. Only JSON as a returned value.
  37. 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. 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. 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. 40. Fetch: history Started by WHATWG. Status (April 2017): Not available in IE11. Available everywhere else. Reasonable poly ll is available.
  41. 41. Fetch: details Uses promises ( nally!). Model: fetch(req).then(res => ...) Allows to handle redirects, CORS.
  42. 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. 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. 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. 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. 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. 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.
  48. 48. Libraries to the rescue!
  49. 49. Big gorillas: Angular/React Both are soft on I/O and data. Variety of possible options. Good for us?
  50. 50. Angular 2/4 — don’t Shahar Talmi (Angular core contributor) wrote: Why I won’t be using Fetch API in my apps It is a telling title.
  51. 51. Angular 2/4 — do Shahar suggests: jQuery’s $.ajax() Angular’s $http SuperAgent Axios — his favorite
  52. 52. Angular’s $http I Main: $http(req).toPromise(). then(res => ...) De nes helpers: get(), head(), post(), put(), delete(), jsonp(), patch()
  53. 53. Angular’s $http II Supports transforming requests and responses. Supports GET and JSONP caching. Supports setting defaults.
  54. 54. Angular’s $http: example this.http.post(url, data).toPromise(). then(res => { console.log(res.data); }).catch(res => { console.log(res.status); });
  55. 55. SuperAgent request .post('/api/pet') .send({name: 'Manny'}) .set('Accept', 'application/json') .end((err, res) => alert(err || !res.ok ? 'Oh no! error' : JSON.parse(res.body)) });
  56. 56. Axios I POST JSON using the main method: axios({method: 'post', url: url, data: data}) // user's then() & catch()
  57. 57. Axios II POST JSON using a helper: axios.post(url, data).then(res => { console.log(res.data); }).catch(res => { console.log(res.status); });
  58. 58. React — do Redux Simple: reducers, actions, middlewares, and stores. Relay Implement GraphQL on servers More modern: Relay Modern
  59. 59. React So how to POST JSON?
  60. 60. React — really??? Answered on StackOver ow: Fetch API with poly ll SuperAgent Axios
  61. 61. Summary We can use whatever. Including our ideal API. Let’s go back to our micro-library.
  62. 62. heya/io
  63. 63. heya/io I https://github.com/heya/io Built using our ideal API. Supports pluggable services. Supports pluggable transports. Thoroughly battle-tested.
  64. 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. 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); } });
  66. 66. Advanced I/O Finally!
  67. 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. 68. Cache issues In HTTP world cache: Controlled by a server. Server cannot recall a response. Client cannot evict a response.
  69. 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. 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. 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. 72. heya/io is extendable II Pipeline extensions: services. XHR replacements: transports. All extensions should be included explicitly. Pay only for what you use.
  73. 73. Extensibility Flexibility Orchestration on top of the ideal API.
  74. 74. heya/io: bust service I var req = io.get({ url: 'abc', bust: true }); // abc?io-bust=1470185125354-507943
  75. 75. heya/io: bust service II var req = io.get({ url: 'abc', bust: 'xyz' }); // abc?xyz=1470185125354-507943
  76. 76. heya/io: bust service III By default: no bust. Con gurable: Bust key. Bust value generating.
  77. 77. heya.io: cache service I Storage-based: Session storage (default). Local storage (permanent). Caches GET requests automatically. To opt out: cache: false
  78. 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. 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. 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. 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. 82. heya/io: mock service IV // redirect io.mock('/abc', () => io.get('/xyz')); io.get('/abc').then(data => { console.log(data); // from /xyz });
  83. 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. 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. 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. 86. heya/io: mock service VIII // server error io.mock('/abc', () => io.mock.makeXHR({status: 500}) );
  87. 87. heya/io: bundle service I Traditional I/O Client Server HTTPconnections HTTPconnections
  88. 88. heya/io: bundle service II Problems with the tradition: We may exceed number of HTTP connections. Potential stalling.
  89. 89. heya/io: bundle service III Problems with the tradition: Each payload is small, and compressed separately. Poor compression.
  90. 90. heya/io: bundle service IV Bundle I/O Client Server HTTPconnections HTTPconnections bundle bundle
  91. 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. 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. 93. heya/io: bundle service VII Bundle’s narrative: Client unbundles responses. Noti es requesters.
  94. 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. 95. heya/io: bundle service IX Assumes a server handler. Reference: heya/bundler. Local server connections are fast and low- lag.
  96. 96. heya/io: bundle service X Bundling helps with compression. Bundling requires just one HTTP connection.
  97. 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. 98. heya/io: bundle service XII Bundler can return responses for unrequested requests. Similar to HTTP/2 Server Push. Cache will be populated.
  99. 99. heya/io: bundle service XIII Behind the scenes: Client collects requests. Sends the array as JSON. Server unpacks. Runs them in parallel.
  100. 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.
  101. 101. heya/io: prefetch I Typical waterfall (served locally):
  102. 102. heya/io: prefetch II What if we call /api before loading anything? Prefetch AKA data forward. The trick is to do it without libraries.
  103. 103. heya/io: prefetch III //<html><head><script> (function () { var xhr = new XMLHttpRequest(); xhr.onload = function () { window._r = JSON.parse(xhr.responseText); }; xhr.open('POST', '/api', true); xhr.send(null); }()); //</script></head></html>
  104. 104. heya/io: prefetch IV Prefetch (served locally):
  105. 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?
  106. 106. heya/io: prefetch VI Typical waterfall (mobile):
  107. 107. heya/io: prefetch VII Prefetch (mobile):
  108. 108. heya/io: prefetch VII It was 16.88s, now it is 15.36s. We saved 1.52s! We deducted almost all /api.
  109. 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
  110. 110. heya/io: summary Micro-library. All code < 5k (min/gzip). No hard dependencies! Simple ideal API. Battle-tested and proven. Works with all libraries.
  111. 111. Summary Correctly using I/O we can: Greatly improve performance. Write clear clean code. Use ideal API. Improve perf for free.
  112. 112. That’s all, folks!