Upgrade to Pro — share decks privately, control downloads, hide ads and more …

fetch is the new XHR

othree
August 16, 2015

fetch is the new XHR

othree

August 16, 2015
Tweet

More Decks by othree

Other Decks in Technology

Transcript

  1. fetch is the new XHR othree @ coscup 2015

  2. XHR • XMLHttpRequest • by Microsoft, 1999 • Make HTTP

    request in web page
  3. Ajax • Most popular pattern based on XHR • Jesse

    James Garrett named it “AJAX” • Foundation of Single Page Application http://www.adaptivepath.com/ideas/ajax-new-approach-web-applications/
  4. XHR Spec • W3C Working Draft since 2006, Level 2

    since 2008 • WHATWG Living Standard since 2011
  5. What’s in 2.0 CORS FormData ArrayBuffer File/Blob

  6. function uploadFiles(url, files) { var formData = new FormData(); for

    (var i = 0, file; file = files[i]; ++i) { formData.append(file.name, file); } var xhr = new XMLHttpRequest(); xhr.open('POST', url, true); xhr.onload = function(e) { ... }; xhr.send(formData); // multipart/form-data } document.querySelector('input[type="file"]') .addEventListener('change', function(e) { uploadFiles('/server', this.files); }, false);
  7. function uploadFiles(url, files) { var formData = new FormData(); for

    (var i = 0, file; file = files[i]; ++i) { formData.append(file.name, file); } var xhr = new XMLHttpRequest(); xhr.open('POST', url, true); xhr.onload = function(e) { ... }; xhr.send(formData); // multipart/form-data } document.querySelector(‘input[type="file"]') .addEventListener('change', function(e) { uploadFiles('/server', this.files); }, false);
  8. function uploadFiles(url, files) { var formData = new FormData(); for

    (var i = 0, file; file = files[i]; ++i) { formData.append(file.name, file); } var xhr = new XMLHttpRequest(); xhr.open('POST', url, true); xhr.onload = function(e) { ... }; xhr.send(formData); // multipart/form-data } document.querySelector(‘input[type="file"]') .addEventListener('change', function(e) { uploadFiles('/server', this.files); }, false);
  9. function uploadFiles(url, files) { var formData = new FormData(); for

    (var i = 0, file; file = files[i]; ++i) { formData.append(file.name, file); } var xhr = new XMLHttpRequest(); xhr.open('POST', url, true); xhr.onload = function(e) { ... }; xhr.send(formData); // multipart/form-data } document.querySelector(‘input[type="file"]') .addEventListener('change', function(e) { uploadFiles('/server', this.files); }, false);
  10. function uploadFiles(url, files) { var formData = new FormData(); for

    (var i = 0, file; file = files[i]; ++i) { formData.append(file.name, file); } var xhr = new XMLHttpRequest(); xhr.open('POST', url, true); xhr.onload = function(e) { ... }; xhr.send(formData); // multipart/form-data } document.querySelector(‘input[type="file"]') .addEventListener('change', function(e) { uploadFiles('/server', this.files); }, false);
  11. None
  12. None
  13. XHR • 15 years old

  14. 赫⻔门 at ShenJS ૄ൅ϖᇀؽ൅ඹ۱ᄅđభ؊׻߶଴၂П

  15. 128x harder than 15 years ago

  16. New Stuffs JSON FormData File/Blob Promise ServiceWorker ArrayBuffer

  17. Not every new tech can apply to XHR without break

    it.
  18. So here is the new XHR.

  19. Fetch

  20. Fetch • The unified architecture of resource fetching • img,

    script, css, cursor image, …etc • fetch() JavaScript API
  21. Fetch • The unified architecture of resource fetching • img,

    script, css, cursor image, …etc • fetch() JavaScript API Today’s
  22. fetch • Promise based • JSON support • Simple naming,

    options object • Designed as low level API
  23. None
  24. None
  25. fetch('./api/some.json').then(function(response) { return response.json(); }).then(function(data) { console.log(data); });

  26. fetch('./api/some.json').then(function(response) { return response.json(); }).then(function(data) { console.log(data); });

  27. fetch('./api/some.json').then(function(response) { return response.json(); }).then(function(data) { console.log(data); });

  28. fetch('./api/some.json').then(function(response) { return response.json(); }).then(function(data) { console.log(data); });

  29. fetch('./api/some.txt').then(function(response) { return response.text(); }).then(function(data) { console.log(data); });

  30. fetch('./api/some.file').then(function(response) { return response.blob(); }).then(function(data) { // console.log(data); });

  31. fetch('./api/some.json') .then(res => res.json()) .then(data => console.log(data));

  32. fetch('./api/entry', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body:

    JSON.stringify(data) }).then(function(response) { response.json().then(data => console.log(data)); return response; });
  33. fetch('./api/entry', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-url-encoded' }, body:

    $.param(data) }); http://api.jquery.com/jquery.param/
  34. fetch('./api/entry', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-url-encoded' }, body:

    $.param(data) }); http://api.jquery.com/jquery.param/
  35. fetch('./api/entry', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-url-encoded' }, body:

    $.param(data) }); http://api.jquery.com/jquery.param/
  36. var obj = { key1: 'value1', key2: [10, 20, 30]

    }; var str = $.param(obj); // "key1=value1&key2%5B%5D=10&key2%5B%5D=20&key2%5B%5D=30" https://www.npmjs.com/package/jquery-param
  37. var obj = { key1: 'value1', key2: [10, 20, 30]

    }; var str = $.param(obj); // "key1=value1&key2%5B%5D=10&key2%5B%5D=20&key2%5B%5D=30" http://www.w3.org/TR/html5/forms.html#application/x-www-form-urlencoded-encoding-algorithm
  38. var obj = { key1: 'value1', key2: [10, 20, 30]

    }; var str = $.param(obj); // "key1=value1&key2%5B%5D=10&key2%5B%5D=20&key2%5B%5D=30" // key1=value1 & // key2[]=10 & // key2[]=20 & // key2[]=30 http://www.w3.org/TR/html5/forms.html#application/x-www-form-urlencoded-encoding-algorithm
  39. Or you can post JSON, or other format

  40. fetch('./api/entry', { method:'POST', headers: { 'Content-Type': 'application/x-www-form-url-encoded' }, body: $.param(data)

    });
  41. method headers body mode crendentials cache

  42. method headers body mode crendentials cache

  43. mode • The one XHR can’t do • same-origin •

    cors • no-cors
  44. no-cors • Don’t check CORS • Get opaque response •

    For ServiceWorker • XHR can’t do this
  45. method headers body mode crendentials cache

  46. headers • POJSO(Plain Old JavaScript Object) { 'Content-Type': 'text/plain', 'X-Custom-Header',

    'ProcessThisImmediately' }
  47. fetch('./api/some.json', { method: 'POST', mode: 'no-cors', headers: { 'Content-Type': 'text/plain',

    'X-Custom-Header', 'ProcessThisImmediately' } });
  48. What if

  49. What if Header have multiple entry with the same name?

  50. Set-Cookie: JSESSIONID=alphabeta120394049; HttpOnly Set-Cookie: AWSELBID=baaadbeef6767676767690220; Path=/alpha https://community.apigee.com/articles/2319/how-to-handle-multi-value-headers-in-javascript.html

  51. RFC 2616 Multiple message-header fields with the same field-name MAY

    be present in a message if and only if the entire field-value for that header field is defined as a comma-separated list [i.e., #(values)] http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
  52. Header Class • New global class • For both request

    and response headers https://developer.mozilla.org/en-US/docs/Web/API/Headers
  53. var h = new Headers(); h.append( 'Set-Cookie', 'JSESSIONID=alphabeta120394049; HttpOnly' );

    h.append( 'Set-Cookie', 'AWSELBID=baaadbeef6767676767690220; Path=/alpha' ); fetch(url, { headers: h });
  54. var h = new Headers(); h.append( 'Set-Cookie', 'JSESSIONID=alphabeta120394049; HttpOnly' );

    h.append( 'Set-Cookie', 'AWSELBID=baaadbeef6767676767690220; Path=/alpha' ); fetch(url, { headers: h });
  55. var h = new Headers(); h.append( 'Set-Cookie', 'JSESSIONID=alphabeta120394049; HttpOnly' );

    h.append( 'Set-Cookie', 'AWSELBID=baaadbeef6767676767690220; Path=/alpha' ); fetch(url, { headers: h });
  56. append() delete() get() getAll() has() set()

  57. Deal with Response

  58. fetch('./api/some.json').then(function(response) { return response.json(); })

  59. fetch('./api/some.json').then(function(response) { return response.json(); })

  60. Response • New global class • Contains status, headers, body

  61. ok status url statusText headers type

  62. ok status url statusText headers type

  63. ok • “ok” is true when status is 2xx

  64. Means • Fetch promise will resolve even when server respond

    404, 500 … • Request is complete so Promise is resolved.
  65. Reject? • On exception, ex: Network Error

  66. arrayBuffer() blob() formData() json() text() bodyUsed

  67. arrayBuffer() blob() formData() json() text() bodyUsed

  68. Response Body • For formatted response body • No auto

    format detection • Body can consume only once
 (save memory) • Use clone when need consume twice
  69. fetch('...').then(function (res) { if (res.ok) { if (res.headers.get('Content-Type') === 'application/json')

    return res.json(); } } })
  70. fetch('...').then(function (res) { if (res.ok) { if (res.headers.get('Content-Type') === 'application/json')

    return res.json(); } } })
  71. fetch('...').then(function (res) { if (res.ok) { if (res.headers.get('Content-Type') === 'application/json')

    return res.json(); } } })
  72. fetch('...').then(function (res) { if (res.ok) { if (res.headers.get('Content-Type') === 'application/json')

    return res.json(); } } })
  73. New Classes • Headers • Response

  74. New Classes • Headers • Response • Request

  75. Request • Another new global class • Contains method, headers,

    body …
  76. Where to Use • Inside fetch • As fetch input

  77. None
  78. None
  79. None
  80. None
  81. Ideally You Can • Create a request object • Reuse

    it
  82. var req = new Request('http://blah', { method: 'POST', body: requestBody

    }); fetch(req); fetch(req);
  83. But • Body can only consumed once

  84. var req = new Request('http://blah', { method: 'POST', body: requestBody

    }); fetch(req); fetch(req); // TypeError
  85. Issue #61 • Fix the different behaviors • Request will

    not able to reuse in the future https://github.com/whatwg/fetch/issues/61
  86. Use fetch Now

  87. None
  88. Polyfill • by Github
 https://github.com/github/fetch

  89. Node Polyfill • node-fetch
 https://www.npmjs.com/package/node-fetch • isomorphic-fetch
 https://www.npmjs.com/package/isomorphic-fetch

  90. Fact • Debug:
 Chrome, at ‘other tab’, request body not

    shows
 Firefox, depend on response type
  91. Fact • Safety flag for CORS not affect fetch 


    Affect Electron app 
 Use XHR + polyfill instead
  92. Fact • Not able to cancel a fetch • Issue

    #27 • No solution now https://github.com/whatwg/fetch/issues/27
  93. Fact • Not support progress now • fetch-with-streams • Future

    spec https://github.com/yutakahirano/fetch-with-streams
  94. Streams • WHATWG Spec • Not Node Stream • Base

    on Promise
  95. fetch(url).then(response => { var reader = response.body.getReader(); var decoder =

    new TextDecoder(); function drain(valueSoFar) { return reader.read().then(function(result) { valueSoFar += decoder.decode(result.value || new Uint8Arr if (result.done) return valueSoFar; return drain(valueSoFar); }); } return drain(); }).then(function(fullText) { console.log(fullText); }); https://github.com/whatwg/fetch/issues/28#issuecomment-87209944
  96. fetch(url).then(response => { var reader = response.body.getReader(); var decoder =

    new TextDecoder(); function drain(valueSoFar) { return reader.read().then(function(result) { valueSoFar += decoder.decode(result.value || new Uint8Arr if (result.done) return valueSoFar; return drain(valueSoFar); }); } return drain(); }).then(function(fullText) { console.log(fullText); });
  97. fetch(url).then(response => { var reader = response.body.getReader(); var decoder =

    new TextDecoder(); function drain(valueSoFar) { return reader.read().then(function(result) { valueSoFar += decoder.decode(result.value || new Uint8Arr if (result.done) return valueSoFar; return drain(valueSoFar); }); } return drain(); }).then(function(fullText) { console.log(fullText); });
  98. fetch(url).then(response => { var reader = response.body.getReader(); var decoder =

    new TextDecoder(); function drain(valueSoFar) { return reader.read().then(function(result) { valueSoFar += decoder.decode(result.value || new Uint8Arr if (result.done) return valueSoFar; return drain(valueSoFar); }); } return drain(); }).then(function(fullText) { console.log(fullText); });
  99. fetch(url).then(response => { var reader = response.body.getReader(); var decoder =

    new TextDecoder(); function drain(valueSoFar) { return reader.read().then(function(result) { valueSoFar += decoder.decode(result.value || new Uint8Arr if (result.done) return valueSoFar; return drain(valueSoFar); }); } return drain(); }).then(function(fullText) { console.log(fullText); });
  100. fetch(url).then(response => { var reader = response.body.getReader(); var decoder =

    new TextDecoder(); function drain(valueSoFar) { return reader.read().then(function(result) { valueSoFar += decoder.decode(result.value || new Uint8Arr if (result.done) return valueSoFar; return drain(valueSoFar); }); } return drain(); }).then(function(fullText) { console.log(fullText); });
  101. Code is complex But when we use ES7

  102. Code is complex But when we use ES7 async/await

  103. fetch(url).then(async response => { var reader = response.body.getReader(); var decoder

    = new TextDecoder(); var fullText = ''; var result; do { result = await reader.read(); fullText += decoder.decode(result.value || new Uint8Array, } while (!result.done); console.log(fullText); }); https://github.com/whatwg/fetch/issues/28#issuecomment-87209944
  104. fetch(url).then(async response => { var reader = response.body.getReader(); var decoder

    = new TextDecoder(); var fullText = ''; var result; do { result = await reader.read(); fullText += decoder.decode(result.value || new Uint8Array, } while (!result.done); console.log(fullText); });
  105. fetch(url).then(async response => { var reader = response.body.getReader(); var decoder

    = new TextDecoder(); var fullText = ''; var result; do { result = await reader.read(); fullText += decoder.decode(result.value || new Uint8Array, } while (!result.done); console.log(fullText); });
  106. fetch(url).then(async response => { var reader = response.body.getReader(); var decoder

    = new TextDecoder(); var fullText = ''; var result; do { result = await reader.read(); fullText += decoder.decode(result.value || new Uint8Array,{ } while (!result.done); console.log(fullText); });
  107. None
  108. Current Status • Lots of new stuff is on the

    way • Streams, cancel • Browser integration • Cache, HTTP/2, CSP, subresource integrity,
 GET with body, busy indicator
  109. Summary • fetch() supports lots of new stuff • Simple

    API design • Easy to use
  110. But • fetch() is low level API • Need write

    extra codes • Hard to deal with general case • So there is fetch-er
  111. fetch-er is the new $.ajax

  112. fetch-er github.com/othree/fetcher

  113. fetch-er • My open source project • A fetch helper,

    like jQuery.ajax • Named ‘fetch-er’ • NPM ‘fetcher’ already taken…
  114. Why • fetch is low level API • Already familiar

    with jQuery.ajax API • Don’t repeat yourself
  115. Repeat? • I must generate request body • I must

    detect response type • I must reject promise if response isn't ok
  116. fetcher.getJSON('/api/users').then(function (data) { console.log(data[0]); });

  117. fetcher.post('/api/users', {name: 'John'})

  118. fetcher.post('/api/users', { name: 'John' }, { contentType: 'json' })

  119. fetcher.method(url, data?, options?)

  120. Features • Auto generate request body • Auto parse response

    type and convert data • jQuery similar behavior • Timeout support • Global options
  121. Dealing Response • Always return an array
 (Promise only accepts

    one value) • [data, statusText, response]
  122. fetcher.get('/api/entry').then(function (res) { var data = res[0]; var status =

    res[1]; var response = res[2]; });
  123. Why not use object?

  124. Why not use object? Better for ES6 destructuring assign

  125. fetcher.get('/api/entry') .then(function ([data, status, response]) { });

  126. fetcher.get('/api/entry') .then(([data, status, response]) => { });

  127. Target User • Want to use ES6 Promise to control

    flow • Don’t want to deal with data formatting stuff • Familiar with jQuery.ajax
  128. TODO • jsonp? • processData

  129. Patch Welcome

  130. One More Thing

  131. sendBeacon • POST data to analytic service • Don’t care

    about response
  132. navigator.sendBeacon('/log', analyticsData);

  133. None
  134. Q?