Slide 1

Slide 1 text

fetch is the new XHR othree @ coscup 2015

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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/

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

What’s in 2.0 CORS FormData ArrayBuffer File/Blob

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

No content

Slide 12

Slide 12 text

No content

Slide 13

Slide 13 text

XHR • 15 years old

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

128x harder than 15 years ago

Slide 16

Slide 16 text

New Stuffs JSON FormData File/Blob Promise ServiceWorker ArrayBuffer

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

So here is the new XHR.

Slide 19

Slide 19 text

Fetch

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

No content

Slide 24

Slide 24 text

No content

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

Or you can post JSON, or other format

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

method headers body mode crendentials cache

Slide 42

Slide 42 text

method headers body mode crendentials cache

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

method headers body mode crendentials cache

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

What if

Slide 49

Slide 49 text

What if Header have multiple entry with the same name?

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

Deal with Response

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

ok status url statusText headers type

Slide 62

Slide 62 text

ok status url statusText headers type

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

Reject? • On exception, ex: Network Error

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

Response Body • For formatted response body • No auto format detection • Body can consume only once
 (save memory) • Use clone when need consume twice

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

New Classes • Headers • Response

Slide 74

Slide 74 text

New Classes • Headers • Response • Request

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

Where to Use • Inside fetch • As fetch input

Slide 77

Slide 77 text

No content

Slide 78

Slide 78 text

No content

Slide 79

Slide 79 text

No content

Slide 80

Slide 80 text

No content

Slide 81

Slide 81 text

Ideally You Can • Create a request object • Reuse it

Slide 82

Slide 82 text

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

Slide 83

Slide 83 text

But • Body can only consumed once

Slide 84

Slide 84 text

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

Slide 85

Slide 85 text

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

Slide 86

Slide 86 text

Use fetch Now

Slide 87

Slide 87 text

No content

Slide 88

Slide 88 text

Polyfill • by Github
 https://github.com/github/fetch

Slide 89

Slide 89 text

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

Slide 90

Slide 90 text

Fact • Debug:
 Chrome, at ‘other tab’, request body not shows
 Firefox, depend on response type

Slide 91

Slide 91 text

Fact • Safety flag for CORS not affect fetch 
 Affect Electron app 
 Use XHR + polyfill instead

Slide 92

Slide 92 text

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

Slide 93

Slide 93 text

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

Slide 94

Slide 94 text

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

Slide 95

Slide 95 text

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

Slide 96

Slide 96 text

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

Slide 97

Slide 97 text

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

Slide 98

Slide 98 text

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

Slide 99

Slide 99 text

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

Slide 100

Slide 100 text

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

Slide 101

Slide 101 text

Code is complex But when we use ES7

Slide 102

Slide 102 text

Code is complex But when we use ES7 async/await

Slide 103

Slide 103 text

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

Slide 104

Slide 104 text

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

Slide 105

Slide 105 text

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

Slide 106

Slide 106 text

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

Slide 107

Slide 107 text

No content

Slide 108

Slide 108 text

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

Slide 109

Slide 109 text

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

Slide 110

Slide 110 text

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

Slide 111

Slide 111 text

fetch-er is the new $.ajax

Slide 112

Slide 112 text

fetch-er github.com/othree/fetcher

Slide 113

Slide 113 text

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

Slide 114

Slide 114 text

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

Slide 115

Slide 115 text

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

Slide 116

Slide 116 text

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

Slide 117

Slide 117 text

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

Slide 118

Slide 118 text

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

Slide 119

Slide 119 text

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

Slide 120

Slide 120 text

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

Slide 121

Slide 121 text

Dealing Response • Always return an array
 (Promise only accepts one value) • [data, statusText, response]

Slide 122

Slide 122 text

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

Slide 123

Slide 123 text

Why not use object?

Slide 124

Slide 124 text

Why not use object? Better for ES6 destructuring assign

Slide 125

Slide 125 text

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

Slide 126

Slide 126 text

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

Slide 127

Slide 127 text

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

Slide 128

Slide 128 text

TODO • jsonp? • processData

Slide 129

Slide 129 text

Patch Welcome

Slide 130

Slide 130 text

One More Thing

Slide 131

Slide 131 text

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

Slide 132

Slide 132 text

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

Slide 133

Slide 133 text

No content

Slide 134

Slide 134 text

Q?