Could Not Do a Physical-order Read to Fetch Next Row.
At that place's been some confusion around the new fetch API recently. Let's clear things upward.
The first matter you lot'll notice about fetch is it's a massive improvement on XMLHttpRequest
in terms of API design. Here's how to become some JSON using XHR:
var xhr = new XMLHttpRequest ( ) ; xhr. open ( 'GET' , url) ; xhr.responseType = 'json' ; xhr. onload = function ( ) { console. log (xhr.response) ; } ; xhr. onerror = role ( ) { console. log ( 'Booo' ) ; } ; xhr. send ( ) ;
At present mop up that vomit and take a look at how fetch does the same thing:
fetch (url) . and so ( function ( response ) { render response. json ( ) ; } ) . then ( function ( data ) { panel. log (data) ; } ) . grab ( function ( ) { console. log ( 'Booo' ) ; } ) ;
Mix in some ES6 arrow functions and information technology gets even more than compact:
fetch (url) . so ( ( r ) => r. json ( ) ) . then ( ( data ) => console. log (data) ) . catch ( ( e ) => panel. log ( 'Booo' ) ) ;
And with ES7 async functions it can exist structured like sync lawmaking, whilst even so being async:
( async ( ) => { try { var response = await fetch (url) ; var information = await response. json ( ) ; console. log (information) ; } take hold of (e) { console. log ( 'Booo' ) ; } } ) ( ) ;
Unfortunately, not everyone was throwing "SHUT UP AND TAKE MY MONEY" memes at it. One particular high-profile JavaScript community member was unconvinced…
They thought we shouldn't be calculation high level features to the platform, especially as we're in dire need of lower level primitives when it comes to requests and responses.
To that I say, well, really, that'south soooo fetch, and we are going to brand fetch happen. Let's articulate upwardly the misconceptions…
Don't be fooled past the nice API
A nice, meaty API can exist a sign of "high level", and therefore restrictive. Simply that isn't the example here. XHR is so bad, a lower-level and more than featureful API tin can exist also simpler and easier to utilize.
XHR is at present defined in terms of fetch (see the calls to fetch in XHR'southward .transport()
), meaning fetch is lower level than XHR.
Fetch isn't done even so
What exists in Chrome today doesn't embrace the full spec, and the spec doesn't cover all of the planned features. In some cases this is because they haven't been designed notwithstanding, in others information technology'south because they're dependent on other in-progress specs. Basically, nosotros're post-obit this pattern:
Bit of a bluster: it bothers me that as developers, we preach iterative evolution and release, but when nosotros're the customers of that arroyo the reaction is all too oftentimes "HOW DARE Yous PRESENT ME WITH SUCH INCOMPLETE IMPERFECTION".
The alternative would take been to sit on the feature for months (or years?) instead of getting large parts of it into developers' hands today. Iterative release too means we've been able to become feedback from existent-globe usage, and that steers future iterations in terms of blueprint and priority.
Anyhow, let'south take a expect at what fetch tin do that XHR cannot:
Asking/Response primitives
XHR kinda smushes the request and response together, meaning they can't be used separately. Fetch is different cheers to the Asking
and Response
constructors. This is especially useful within a ServiceWorker:
self. addEventListener ( 'fetch' , role ( issue ) { if (event.request.url === new URL ( '/' , location) .href) { effect. respondWith ( new Response ( '<h1>Hi!</h1>' , { headers: { 'Content-Type' : 'text/html' } , } ) , ) ; } } ) ;
In the above, outcome.asking
is a Asking
. There'due south no response yet, and instead of letting the browser go to the network I respond with my ain Response
. Alternatively, I could get the response from the network using fetch(event.asking)
, or fifty-fifty get a response from the enshroud.
The Enshroud API is a shop of Response
s keyed against Request
s, having separates allows you to add your own pairings.
This is in Chrome stable today from within a ServiceWorker. The fetch API is besides available from pages in Chrome Beta.
Soon, request.context
will exist able to tell yous the source of that request, so you tin tell apart requests triggered by hyperlinks vs <img>
etc.
'no-cors' and opaque responses
If I request //google.com
from this site using XHR or plain fetch it will fail. This is because it'due south a CORS request and the response doesn't have CORS headers.
Yet, with fetch, you tin make a no-cors
request:
fetch ( '//google.com' , { manner: 'no-cors' , } ) . and then ( function ( response ) { console. log (response.blazon) ; // "opaque" } ) ;
This is like to the request an <img>
makes. Of course, you tin't read the content of the response as information technology could contain private information, but it can be consumed by other APIs:
cocky. addEventListener ( 'fetch' , function ( event ) { upshot. respondWith ( fetch ( '//www.google.co.uk/images/srpr/logo11w.png' , { way: 'no-cors' , } ) , ) ; } ) ;
The above is fine within a ServiceWorker, as long every bit the receiver is happy with a no-cors
response. <img>
is, <img crossorigin>
isn't.
Yous can also store these responses in the Cache API for use later on, which is great for CDN content such as scripts, CSS, and imagery, which often lack CORS headers.
For more on the origin of CORS, come across Anne VK'south article on same-origin policy.
This all works in Chrome stable today. In Chrome Canary, although y'all can use fetch()
from a page, no-cors
isn't enabled there yet (ticket).
Streams
XHR lacks streaming. You can get admission to .responseText
while the request is in progress, but the whole response is still going to buffer into memory.
With fetch, you lot become access to the low-level body stream. Say I wanted to load a massive CSV and find the value in the cell after the one containing "Jake":
fetch ( '/large-data.csv' ) . and then ( function ( response ) { var reader = response.body. getReader ( ) ; var partialCell = '' ; var returnNextCell = false ; var returnCellAfter = 'Jake' ; var decoder = new TextDecoder ( ) ; function search ( ) { return reader. read ( ) . then ( function ( outcome ) { partialCell += decoder. decode (result.value || new Uint8Array ( ) , { stream: !upshot.done, } ) ; // Split what nosotros have into CSV 'cells' var cellBoundry = / (?:,|\r\n) / ; var completeCells = partialCell. separate (cellBoundry) ; if ( !result.done) { // Concluding cell is likely incomplete // Go along hold of it for next time partialCell = completeCells[completeCells.length - one ] ; // Remove it from our complete cells completeCells = completeCells. slice ( 0 , - 1 ) ; } for ( var cell of completeCells) { jail cell = jail cell. trim ( ) ; if (returnNextCell) { reader. abolish ( 'No more reading needed.' ) ; return jail cell; } if (cell === returnCellAfter) { returnNextCell = true ; } } if (result.done) { throw Error ( 'Could not find value later ' + returnCellAfter) ; } return search ( ) ; } ) ; } return search ( ) ; } ) . so ( office ( consequence ) { panel. log ( "Got the outcome! It's '" + result + "'" ) ; } ) . grab ( part ( err ) { panel. log (err.message) ; } ) ;
Here I'm reading through the CSV (yep, I know my regex is naive), but with only a chunk of content in memory at a given time. In one case I find the value I'm looking for, I cancel the stream, closing the connection.
response.torso
is a ReadableStream
as defined by the streams spec. Streaming was planned from the outset, but it's one of the bits nosotros launched without as the spec was still in progress.
TextDecoder
is part of the encoding spec. If the chunk information technology receives via .decode(input, {stream: true})
ends with a partial multi-byte grapheme, it will return and flush everything just that partial. The adjacent phone call to decode appends onto the partial, hopefully forming a whole character.
This stuff is starting to land in Canary, here'due south a demo of the above, and hither'due south a demo with a larger dataset (alert: running the demo may download many megabytes).
Streams are one of the things I'm actually looking forward to having on the platform. I want to exist able to stream-parse some JSON, generate some HTML as a consequence, and stream that to the browser'southward parser. JS-driven apps lack an easy fashion to get progressive-rendering from a single data source, streams can solve that.
Transform streams are coming soon, which would brand the lawmaking higher up simpler. Ideally TextDecoder
would be a transform stream, and some other transform stream could chunk it into CSV rows. Something like:
fetch ( '/large-data.csv' ) . and so ( function ( response ) { var csvStream = response.trunk . pipeThrough ( new TextDecoder ( ) ) . pipeThrough ( new CSVDecoder ( ) ) ; csvStream. read ( ) . then ( function ( result ) { // array of cell values for the first row panel. log (consequence.value) ; } ) ; } ) ;
Transform streams also become really exciting within a ServiceWorker:
self. addEventListener ( 'fetch' , function ( event ) { event. respondWith ( fetch ( 'video.unknowncodec' ) . then ( office ( response ) { var h264Stream = response.body . pipeThrough (codecDecoder) . pipeThrough (h264Encoder) ; return new Response (h264Stream, { headers: { 'Content-type' : 'video/h264' } , } ) ; } ) , ) ; } ) ;
In the to a higher place, I'thou using transform streams to take a video the browser doesn't empathize, decode it with JS, and encode information technology in a format the browser does understand. It'd be amazing to meet if the browser could do this in real time.
Stream readers & cloning
Every bit I mentioned before, we initially shipped fetch without streams back up and then developers could become the other benefits sooner. To make up for a lack of streams & to later on offer a simple way to get mutual information types, we added some readers:
fetch (url) . so ( part ( response ) { return response. json ( ) ; } ) ;
That, every bit you might expect, reads the whole stream as JSON. Here's the total list of readers:
-
.arrayBuffer()
-
.blob()
-
.formData()
-
.json()
-
.text()
They exist on Request
objects too equally responses, and then you lot can use them to read (for example) Mail data inside a ServiceWorker.
These are true stream readers, meaning they bleed the stream:
fetch (url) . then ( part ( response ) { return response. json ( ) . grab ( function ( ) { // This does not work: return response. text ( ) ; } ) ; } ) ;
The call to .text()
fails as the stream has already been read. You can piece of work around this using .clone()
:
fetch (url) . then ( office ( response ) { render response . clone ( ) . json ( ) . catch ( function ( ) { render response. text ( ) ; } ) ; } ) ;
.clone()
opts you into buffering. The clone gets read as JSON, only the original is even so at that place and can exist read in another format. Of course, this means the raw response information needs to exist kept around in retention until all copies are read or garbage nerveless.
Alternatively, yous could wait at the headers of the response:
fetch (url) . then ( function ( response ) { if (response.headers. get ( 'Content-Type' ) === 'application/json' ) { return response. json ( ) ; } render response. text ( ) ; } ) ;
This is another feature fetch has over XHR, you lot can decide which format to read the body as subsequently you've inspected the headers.
Other bits
In that location are more features that fetch has over XHR that I'm not going to encompass in besides much detail, they include:
Fetch has a headers class which tin can be used to read/write headers, and has an ES6 iterator.
Enshroud control
The cache mode lets you specify the interaction with the cache. As in, should the cache be consulted? Should the response get into the cache if it'southward valid? Should the response but come from the cache?
The latter is a scrap contentious as it can expose user history, so it may come with a CORS restriction before information technology lands in Chrome.
No-credential aforementioned-origin requests
XHR forces yous to serve credentials with requests to the same origin, fetch doesn't. In fact, no-credentials is the default for all requests made by fetch, making it less magic than XHR.
What'due south missing?
Of course, there are some features XHR has that fetch doesn't have.
Request aborting
This is the big one. In Canary you can cancel the stream, but there'southward no mode to abort a request before headers have arrived.
We're going to fix this using cancellable promises, which other specs will benefit from. Track the discussion of this over at GitHub.
Progress events
Progress events are a loftier level characteristic that won't arrive in fetch for at present. Y'all tin create your own past looking at the Content-Length
header and using a pass-through stream to monitor the bytes received.
This means you can explicitly handle responses without a Content-Length
differently. And of course, even if Content-Length
is at that place information technology can be a lie. With streams you can handle these lies still you lot want.
Synchronous requests
Noooope. The fetch spec documents them, but they won't be office of the API. Sync requests are awful.
Couldn't this have been congenital on summit of XHR?
XHR was an ugly infant and time has not been kind to it. Information technology's 16 at present. In a few years information technology'll be old enough to drink, and it's enough of a pain in the arse when it'southward sober.
Certain, a lot of this stuff could take been hacked on summit of XHR, simply it would have been a hack. Not only did fetch give us an opportunity to add lower-level features without the cruft of a badly designed API, it immune usa to create a better API for the unproblematic things, using modern JavaScript features similar promises and iterators.
If you want to end using XHR today, in that location's a fetch polyfill. This is built on superlative of XHR, so information technology can't do the stuff that XHR tin can't, but information technology does give you the benefits of a nicer API.
Let'due south make fetch happen!
Farther reading
- Intro to ServiceWorkers
- ES6 iterators
- ES7 async functions
- Partial fetch polyfill - congenital on top of XHR
Thanks to Matt Gaunt, Mat Scales, Anne van Kesteren, Domenic Denicola, Mikeal Rogers, Ben Kelly, and Joshua Bell for disguising the fact I can't really spell, grammar, or indeed code.
Source: https://jakearchibald.com/2015/thats-so-fetch/
0 Response to "Could Not Do a Physical-order Read to Fetch Next Row."
Post a Comment