$30 off During Our Annual Pro Sale. View Details »

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

    View Slide

  2. XHR
    • XMLHttpRequest
    • by Microsoft, 1999
    • Make HTTP request in web page

    View Slide

  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/

    View Slide

  4. XHR Spec
    • W3C Working Draft since 2006, Level 2 since
    2008
    • WHATWG Living Standard since 2011

    View Slide

  5. What’s in 2.0
    CORS FormData
    ArrayBuffer File/Blob

    View Slide

  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);

    View Slide

  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);

    View Slide

  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);

    View Slide

  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);

    View Slide

  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);

    View Slide

  11. View Slide

  12. View Slide

  13. XHR
    • 15 years old

    View Slide

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

    View Slide

  15. 128x harder than 15 years ago

    View Slide

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

    View Slide

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

    View Slide

  18. So here is the new XHR.

    View Slide

  19. Fetch

    View Slide

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

    View Slide

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

    View Slide

  22. fetch
    • Promise based
    • JSON support
    • Simple naming, options object
    • Designed as low level API

    View Slide

  23. View Slide

  24. View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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;
    });

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  39. Or you can post JSON, or other format

    View Slide

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

    View Slide

  41. method headers
    body mode
    crendentials cache

    View Slide

  42. method headers
    body mode
    crendentials cache

    View Slide

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

    View Slide

  44. no-cors
    • Don’t check CORS
    • Get opaque response
    • For ServiceWorker
    • XHR can’t do this

    View Slide

  45. method headers
    body mode
    crendentials cache

    View Slide

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

    View Slide

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

    View Slide

  48. What if

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  52. Header Class
    • New global class
    • For both request and response headers
    https://developer.mozilla.org/en-US/docs/Web/API/Headers

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  56. append() delete()
    get() getAll()
    has() set()

    View Slide

  57. Deal with Response

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  61. ok status
    url statusText
    headers type

    View Slide

  62. ok status
    url statusText
    headers type

    View Slide

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

    View Slide

  64. Means
    • Fetch promise will resolve even when server
    respond 404, 500 …
    • Request is complete so Promise is resolved.

    View Slide

  65. Reject?
    • On exception, ex: Network Error

    View Slide

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

    View Slide

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

    View Slide

  68. Response Body
    • For formatted response body
    • No auto format detection
    • Body can consume only once

    (save memory)
    • Use clone when need consume twice

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  73. New Classes
    • Headers
    • Response

    View Slide

  74. New Classes
    • Headers
    • Response
    • Request

    View Slide

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

    View Slide

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

    View Slide

  77. View Slide

  78. View Slide

  79. View Slide

  80. View Slide

  81. Ideally You Can
    • Create a request object
    • Reuse it

    View Slide

  82. var req = new Request('http://blah', {
    method: 'POST',
    body: requestBody
    });
    fetch(req);
    fetch(req);

    View Slide

  83. But
    • Body can only consumed once

    View Slide

  84. var req = new Request('http://blah', {
    method: 'POST',
    body: requestBody
    });
    fetch(req);
    fetch(req); // TypeError

    View Slide

  85. Issue #61
    • Fix the different behaviors
    • Request will not able to reuse in the future
    https://github.com/whatwg/fetch/issues/61

    View Slide

  86. Use fetch Now

    View Slide

  87. View Slide

  88. Polyfill
    • by Github

    https://github.com/github/fetch

    View Slide

  89. Node Polyfill
    • node-fetch

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

    https://www.npmjs.com/package/isomorphic-fetch

    View Slide

  90. Fact
    • Debug:

    Chrome, at ‘other tab’, request body not shows

    Firefox, depend on response type

    View Slide

  91. Fact
    • Safety flag for CORS not affect fetch 

    Affect Electron app 

    Use XHR + polyfill instead

    View Slide

  92. Fact
    • Not able to cancel a fetch
    • Issue #27
    • No solution now
    https://github.com/whatwg/fetch/issues/27

    View Slide

  93. Fact
    • Not support progress now
    • fetch-with-streams
    • Future spec
    https://github.com/yutakahirano/fetch-with-streams

    View Slide

  94. Streams
    • WHATWG Spec
    • Not Node Stream
    • Base on Promise

    View Slide

  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

    View Slide

  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);
    });

    View Slide

  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);
    });

    View Slide

  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);
    });

    View Slide

  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);
    });

    View Slide

  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);
    });

    View Slide

  101. Code is complex
    But when we use ES7

    View Slide

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

    View Slide

  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

    View Slide

  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);
    });

    View Slide

  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);
    });

    View Slide

  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);
    });

    View Slide

  107. View Slide

  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

    View Slide

  109. Summary
    • fetch() supports lots of new stuff
    • Simple API design
    • Easy to use

    View Slide

  110. But
    • fetch() is low level API
    • Need write extra codes
    • Hard to deal with general case
    • So there is fetch-er

    View Slide

  111. fetch-er is the new $.ajax

    View Slide

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

    View Slide

  113. fetch-er
    • My open source project
    • A fetch helper, like jQuery.ajax
    • Named ‘fetch-er’
    • NPM ‘fetcher’ already taken…

    View Slide

  114. Why
    • fetch is low level API
    • Already familiar with jQuery.ajax API
    • Don’t repeat yourself

    View Slide

  115. Repeat?
    • I must generate request body
    • I must detect response type
    • I must reject promise if response isn't ok

    View Slide

  116. fetcher.getJSON('/api/users').then(function (data) {
    console.log(data[0]);
    });

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  120. Features
    • Auto generate request body
    • Auto parse response type and convert data
    • jQuery similar behavior
    • Timeout support
    • Global options

    View Slide

  121. Dealing Response
    • Always return an array

    (Promise only accepts one value)
    • [data, statusText, response]

    View Slide

  122. fetcher.get('/api/entry').then(function (res) {
    var data = res[0];
    var status = res[1];
    var response = res[2];
    });

    View Slide

  123. Why not use object?

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  127. Target User
    • Want to use ES6 Promise to control flow
    • Don’t want to deal with data formatting stuff
    • Familiar with jQuery.ajax

    View Slide

  128. TODO
    • jsonp?
    • processData

    View Slide

  129. Patch Welcome

    View Slide

  130. One More Thing

    View Slide

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

    View Slide

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

    View Slide

  133. View Slide

  134. Q?

    View Slide