getify / You-Dont-Know-JS

A book series on JavaScript. @YDKJS on twitter.
Other
179.66k stars 33.51k forks source link

Does browser based JS have any way to get data synchonously? #509

Closed govpack closed 9 years ago

govpack commented 9 years ago

Recently this error warning has been showing up in chrome: "Synchronous XMLHttpRequest on the main thread is deprecated because of its detrimental effects to the end user's experience. For more help, check http://xhr.spec.whatwg.org/."

because "Synchronous XMLHttpRequest outside of workers is in the process of being removed from the web platform."

When the majority of task based programming (50-60 years of history) is synchronous, there should be a way to simply Get/Set values without having to descend into callback hell.

I have some variables i wish to retrieve in a browser based App the data is somewhere between 0 and 1ms away right there on the local disk...

ms=Date.now(); data=j.o('A.5.in|get|l'); Date.now()-ms // --> returns after a 0 or 1 millisecond delay

I am quite happy to wait the 1 millisecond for the data to arrive!! since the data will be needed for the next step, i choose to wait and block, that is my desired use case, because it gives far greater simplicity for getting and consuming that data.

we have been able to do this for years, by doing something as straightforward as: xhr=new XMLHttpRequest(); xhr.open('GET','../../../../data_file',false); xhr.responseText

but now some busy body at the whatwg.org wish to take back, reject, and BREAK 15 years of being able to read a bit of data directly!!! I don't want to make every variable into a function. The thing is I thought that was only deprecated?? the thing is async=false, flat out doesn't work anymore!!! (it would help if you called send, lol)

So my question is: Does browser based JS have any way to get data synchronously? or has some one chosen for us that we all have to swallow the red pill?

getify commented 9 years ago

Your question doesn't seem to be about the books per se, so this is a little far afield. But I'll relate my answer back to the books, which is why I'll answer this.


You probably won't find anyone who takes the design/implementation of browser tech seriously who will agree with you that synchronous XHR is ever the way to go. I understand the aesthetics of synchronous programming -- it's attractive -- but blocking the main event loop thread for I/O is just never a good idea.

There's definitely no justification to leave such a feature in the platform, to be widely abused, simply because of extremely niche cases where there's some sort of 1ms guarantee (btw, I extremely doubt that's the case, even in local disk access -- disk I/O is notorious for having potential for slow blocking).

HOWEVER, ES6 has a wonderful answer to this problem, IMO. It's the generator+promise pattern. I cover this in Ch4 "Generators + Promises" of Async & Performance. It's also covered in Ch4 "Generators + Promises" of ES6 & Beyond if you'd like an additional examples. And here's a blog post I wrote that gives a similar perspective.

Briefly:

function *doSomething() {
   var data = yield fetchData( " .. " );
   console.log( data );
}

run( doSomething );

The yield keyword creates localized blocking semantics inside the generator, such that it pauses and waits for a potentially asynchronous action to complete before moving on. The key is, it doesn't actually block the whole program, or the JS event loop... only the code inside the generator. So you get the aesthetic of synchronous programming (which is easier on our brains) but none of the drawbacks of actual synchronous I/O.

The "catch" is that you need, currently, a lib function like run(..) implied above to handle the plumbing of making that work. Many async libs provide one. In my asynquence lib, it's called runner(..).

In the next version of JS (ES2016/ES7), we expect to see async / await added, to provide native syntax support that reduces/eliminates the need for this library runner plumbing to make this pattern work.

prust commented 9 years ago

@getify, sorry for barging in, I realize this is off-topic and is opinion-based, not related to your books. Hope you will forgive me if I respond to this:

you get the aesthetic of synchronous programming (which is easier on our brains) but none of the drawbacks of actual synchronous I/O

I've gone down this road, converting a large desktop app that used custom synchronous APIs on a custom-built browser into async APIs using the yield keyword and Holowaychuk's co helper library.

On the surface it looks attractive -- yield looks nicer than nested callbacks and the local variables do have the same state they did when control left the function. But appearances are deceiving: member variables, globals and outer-scope variables can all be quite different than before the yield keyword. This kind of a dramatic difference in state may actually be better represented by a nested callback function than by a single keyword.

But, syntax aside, using yield or async does not bring back the simplicity of synchronous programming. Yes, they help some surface-level issues by preserving the state of local variables and cleaning up the syntax, but they don't do much to help with the hard problems that stem from the fact that, because the event loop was not blocked, the state of the program has changed. Async programming opens the door to race conditions and non-determinism. By the time the callback is called, the user may have moved to a different screen, objects that the code depends on may be torn down or may have changed in other ways.

The Node.js community will admit it readily: async programming is hard. Promises, generators, async and await keywords, etc, help ease the pain a little, but they don't make the hard problems go away.

In highly-concurrent server code, the benefits of asynchronous programming usually outweigh the costs (with the exception of one-time startup code -- this is why node's require(), for instance, is synchronous).

But for client-side applications, I would argue that it is rare for async disk I/O to be worth the cost, unless you're doing a truly long-running disk operation like vacuuming or backing up an entire database. Node 0.12 introduced synchronous child-process execution because they realized that the benefit of async wasn't worth the cost for CLI apps like build tools.

At any rate, whether the added complexity of async is worth the cost should be a decision left in the hands of developers. And, IMO, wise developers would optimize last and only go async on long-running tasks once it's clear that the cost is worth it -- and reap the benefits of a simpler, more stable and easier-to-maintain codebase.

The browser vendors (and standards bodies) have made it clear that they don't trust developers to make sync vs async performance tradeoff decisions. Perhaps they are concerned that if a tab is not responding that the user will blame the browser instead of the app. Forcing async may result in higher-performing web apps, but it comes at the cost: either more bugs or fewer features. IMO, for 80% of disk I/O, this tradeoff isn't worth it.

Thankfully, devs can turn away from the browsers to other platforms, like node-webkit and atom, that provide powerful, low-level APIs and put choice back in the hands of developers.

govpack commented 9 years ago

@getify , @prust

thanks, i appreciate the answers, Kyle you've done a great job of learning and presenting in depth what's in and up coming in JS, if you were running for congress or some such, i would vote for you!!!

this is not an issue with the excellent and provocatively titled "You-Dont-Know-JS" series of books, but more of a fault with the webplatform/browser, i just thought you might know how to get a little bit of synchronous data from browser side JS, but they've taken the xhr async=false flag away

i'm open minded about the new things like aysnc/await, and will check out those suggested patterns, as i am very interested in all things JS, but i'm more of an ES3 person, i upgraded to iojs just to get execSync() and require() is a clear admission that pure async is an impossible and misleading goal, i think people have gone a little bit too far in that direction. (all tasks take some time to run, sometimes it's better just to wait and have the result directly)

i find the async style of coding takes me 5x longer to write (so anything that can make that at least look, tidier and more straight forward is a welcome thing, i eggarly async await or whatever the new syntax is) i'm pretty sure asynchronous programming is a VERY macho thing, and once i have created one of these monstrosities i am left with something i dread going back into to fix, when it gets all hung up and floaty under load (i thought i had it working, and tested it and it did work, but then when using it for real, to write out 80,000 files i was heartbroken to see the process overheat and crash time after time, for the life of me, i couldn't see how to fix it, or dispose of whatever was being held on to, or how to release such because it was somewhere in callback la la land, lol, So next time i thought i would write an api to get small amounts of config data synchronously, like require() or a script tag in the head would do!! but at runtime, so why is that simple use case denied to me? it was there before by just setting document.scripts[0].src='data.js', there are 1001 way to muck up or end up with bad UX, taking away choice doesn't help fix that, it just removes a basic way to access data in a simple fashion, and forces people into a more complicated paradigm)

i pretty much agree with everything @prust had to say, and will be moving my app to an Electron Shell, in the mean time i'm happy to pass callbacks around like an Olympic athlete passes a baton

and wish you all the best with whatever you are up to