fetch is the new XHR

C4ce16f549c450f4759eb37f5d5d1a63?s=47 othree
August 16, 2015

fetch is the new XHR

C4ce16f549c450f4759eb37f5d5d1a63?s=128

othree

August 16, 2015
Tweet

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?