10

I know it was asked here many times and many times answered this is not a way how it should be done, but once more again :)

Is it possible, somehow, to call async function (such as timer / ajax call), basically common async task and synchronously wait until it ends without having 100%CPU usage and blocked browser?

Simple answer is enough - yes or no. If no i have to write all code depending on the async operation in "async way" Otherwise, it would be better ;)

imagine something like:

updateCSS("someurl.css")

function updateCSS(url) {

   var css = getCachedResource(url);

   css = css.replace(/regexp/gm, function(curUrl) {
     base64 = atob(getCachedResource(curUrl))
     return "data:image;base64," + base64
   })

}

function getCachedResource(url) {
  
  //...
  
  if (cached) {
    
    return cached
    
  } else {
    
    // doajax...    
    // watfor
    
    return loaded
  }
  
}

Of course, I can make it async, but the code will be.... terrible. Moreover, I have to make async all functions calling this function. Can you see simple solution? Blocking would be simplest I would say.

Thanks

Fis
  • 787
  • 2
  • 10
  • 24
  • 5
    The answer is **no**. Welcome to programming in JavaScript :) – Pointy Jan 25 '17 at 01:54
  • 1
    there is absolutely nothing that can turn asynchronous to synchronous. If you stop and think about it, it's obvious why. `the code will be.... terrible` - embrace asynchronicity and the code can be beautiful again – Jaromanda X Jan 25 '17 at 01:56
  • Yup. Sad :) I'll never understand why they didn't implement wait for async op. – Fis Jan 25 '17 at 01:57
  • Jaro: it will be terrible as I have to: a) check all possible changes, b) load resources and while it will be done c) replace. Nice 3 lines to 40 others... Or of course, replacing while loading, bla bla bla, a lot of complicated stuff. – Fis Jan 25 '17 at 01:59
  • wont help you in the browser, but nodejs has a [deasync](https://github.com/abbr/deasync) module – dtkaias Jan 25 '17 at 02:00
  • Can't help me. But thanks for the point – Fis Jan 25 '17 at 02:01
  • When `async/await` lands in enough browsers to make it practical to use, the answer will *kind-of* be **yes**. Kind-of. – Pointy Jan 25 '17 at 02:04
  • as far as understand async/await it can't help in this case so much – Fis Jan 25 '17 at 02:07
  • Mostly it untangles the issue of working with Promises (to some extent). – Pointy Jan 25 '17 at 02:12
  • I had to read this first... https://developer.mozilla.org/cs/docs/Web/JavaScript/EventLoop Makes sense its not possible in any way. – Fis Jan 25 '17 at 02:29
  • 1
    @Pointy - I think it's dangerous to make people think that async/await are pausing execution and waiting because they aren't. They are syntactic sugar which tells the interpreter to implicitly wrap the code that follows in an automatic `.then()` handler. Other JS can still run in response to other events just like today. They do make the code prettier, but don't change what you can/can't do at all. I'm worried that async/await will lead to more and more people not understanding how asynchronous code really works. – jfriend00 Jan 25 '17 at 02:56
  • @jfriend00 well I wasn't trying to spread misinformation :) Your caveats are essentially what I abbreviated as "kind-of". It certainly does change the way you write code, but it definitely does not make asynchronous code into synchronous code. – Pointy Jan 25 '17 at 02:58
  • And yes I predict that async/await will make it much easier to write code for people who understand what's going on, and much harder for people who don't. I don't know what to do about that, but then I really don't understand the phenomenon of people setting out to do complicated programming tasks without any programming background at all. I'm just old. – Pointy Jan 25 '17 at 02:59
  • @Pointy -Yeah, I'm old school on that too. Can't believe how many people are writing code without understanding what they're doing or how stuff works - but then I wrote in assembly language for 2 years before I touched a higher level language. I guess it's why there's so much bad code in the world. Agree, async/await will be nice for people who know what they're doing. Probably even more dangerous for people who don't. But, far too many people here on SO posturing that async/await will solve all asynchronous programming challenges which just won't be the case. – jfriend00 Jan 25 '17 at 03:06
  • @jfriend00 I am often entertained by the fantasy that there's a stackexchange like this for civil engineers or surgeons. – Pointy Jan 25 '17 at 03:12
  • `Probably even more dangerous for people who don't` - indeed for those people the shift will be from thinking `Promises` can majick asynchronous code be synchronous, to thinking that `async/await` will do the impossible, because you know ... it `awaits` – Jaromanda X Jan 25 '17 at 03:47
  • Did your question get answered? If so, please mark the best answer by clicking the green checkmark to the left of it. This will indicate to the community that your question has been answered and will earn you some reputation points for following the proper procedure. – jfriend00 Mar 24 '17 at 04:52

7 Answers7

3

So just to summarize....

The answer is NO. It will never be possible to block the function or any other code and wait until the async operation is done. There is no mechanism for it supported directly by the JavaScript VM.

Especially in browser environment where it is not possible to manipulate the JavaScript virtual machine event loop (as it is possible with Node and how actually "deasync" library is doing it).

It is because of the JavaScript concurrency model and event processing as mentioned here:

https://developer.mozilla.org/cs/docs/Web/JavaScript/EventLoop

It has nothing to do with the fact the JavaScript is single threaded.

Also, please note, all stuff like Promises and new features like await/async are just a different way, how to write/manage the code for asynchronous tasks. It is not and never will be helpful in turning the async to sync.

So don't spend time, like me, by trying to find a way how to do it. Rather spend it by designing the async code ;)

Fis
  • 787
  • 2
  • 10
  • 24
0

As others have pointed out, the short answer is no: Javascript is single-threaded in most environments in which it's used. What you what to have happen is to do your processing of the CSS in another callback from getCachedResource()

Something like this:

function updateCSS(url) {
    getCachedResource(url, function(css) {
        // process the css
    });
}

function getCachedResource(url, done) {
    //...
    if (cached) {
        cachedCSS = ...
        done(cachedCSS);
    } else {
        $.ajax(
        {
            // ...
            success: function(result) {
                // Get the css from the response
                done(css);
            }
            // ...
        });
    }
}

Take a look at jQuery "deferred" objects or Javascript promises for a more complete way of dealing with callbacks

Ray Fischer
  • 936
  • 7
  • 9
  • Unfortunately, its not only about single threading but more about the concurency model and event loop javascript vm internally does. As I posted above, I had to read this first: https://developer.mozilla.org/cs/docs/Web/JavaScript/EventLoop then it would be clear to me that what is async can't never be done sync. And it does not matter which pattern or syntactic sugar i'll use... Callbacks, promises, async, await... Nothing will prevent function from returning, everything is simply... async and internally works in the same way. Call async ->event done->check error->call done or error code. – Fis Jan 25 '17 at 10:31
  • Btw. If you took a look to my cote the main issue is not in accessing of the first resource, but image resources in the following replacer "loop" which must be written in completely different way async than in the example I gave you. It will be something like get css -> asyncdone -> get all urls from css to array -> get all arrayed resources (cache/server ajax) -> asyncresourcedone -> replace(url, atob(resource)) -> alldone? yes, donecallback or, by promise nesting but the real code graph will be almost the same. – Fis Jan 25 '17 at 10:38
  • It's a paradigm shift: Javascript in an async environment doesn't really work by returning values from function. It works by passing values onto the next function. In your example you just need to repeat the pattern in the inner section. Call a routine to get the information and pass it a new function to process the result. Remember also that Javascript functions have access to the local variables of the enclosing functions – Ray Fischer Jan 28 '17 at 00:08
  • async/await/promises solved my problem after solving another one with 3rd party libs. Didn't want to believe it will be that nice (except the generated ECMA5.) – Fis Jan 29 '17 at 00:00
0

Yes.

You want your code to "sleep" until an "async function (such as timer / ajax call) ... ends without having 100%CPU usage". This is exactly what async/await does.

Other answers and comments are fixating on your phrase "synchronously wait", which in JS means blocking, but I understand you to just want to avoid having to refactor your code "the async way". You want to write straightforwardly, without structuring everything as numerous callbacks.

To use async/await, you first have to embrace promises - the space/time of async JavaScript. You then need a modern browser like Chrome or Firefox Developer Edition, or transpile your code with Babel js.

Next, you want to look at fetch, the promise-returning modern way to do ajax.

Now you have everything you need. Here's an example that works in the aforementioned browsers:

async function fetchy(...args) {
  let response = await fetch(...args);
  if (!response.ok) throw new Error((await response.json()).error_message);
  return response;
}

async function showUser(user) {
  let url = `https://api.stackexchange.com/2.2/users/${user}?site=stackoverflow`;
  let item = (await (await fetchy(url)).json()).items[0];
  console.log(JSON.stringify(item, null, 2));

  let blob = await (await fetchy(item.profile_image)).blob();
  img.src = URL.createObjectURL(blob);
}

input.oninput = async e => await showUser(e.target.value);
showUser(input.value);
<input id="input" type="number" value="918910"></input><br>
<img id="img"></img>
<pre id="pre"></pre>

FYI, fetchy is my helper for sane fetch error handling.

Community
  • 1
  • 1
jib
  • 40,579
  • 17
  • 100
  • 158
  • 1
    I think you really should start your answer with "No, but" instead of "Yes". The `await` keyword lets your code sleep/wait indeed, but not synchronously. – Bergi Jan 25 '17 at 05:04
  • @Bergi I hope my answer clarifies what yes means. The question doesn't know what it's asking, using both "sleep" and "sync wait" in the same sentence (an oxymoron in JS). Thus we have to guess what's desired. My guess is users with this question pine for synchronous-*looking* code that's not "terrible". – jib Jan 25 '17 at 05:23
  • You really need to fix your answer. The OP explicitly asks for "synchronously wait". That is NOT what async/await does. It does not turn async code into synchronous code. Other event handlers will run. Concurrency issues can exist if you think nothing else is running while waiting for the async result. async/await is just syntactical sugar. It does not change how the code works. It just makes it more pleasant to write the syntax for async code. – jfriend00 Jan 25 '17 at 08:25
  • And, answers that lead people (particularly newbies who don't want to learn how to property write async-aware code) to believe it magically solves all async programming issues are dangerous and will just lead to trouble for those who believe them and even more trouble when they start writing code. async/await will be a nice tool for those who understand what it does and how it works. It does not allow you to magically write async code without any understanding of what is really going on the async world. – jfriend00 Jan 25 '17 at 08:27
  • Await will never wait. Internally, its another "callback" function called when something is asynchronously done. It does not block or wait at all. – Fis Jan 25 '17 at 10:45
  • @Fis `await` absolutely waits, hence the name. It effectively "sleeps", keeping your context intact and blocking its progress (like a generator) until the asynchronous operation "ends without having 100%CPU usage and blocked browser". This is the closest thing to approximating a thread context in single-threaded JavaScript, short of web workers. What more do you want? – jib Jan 25 '17 at 13:12
  • @jib It does not, trust me :) Its the same as if you say the callback waits till async operation will end. Both are true, but... not answer to my question. – Fis Jan 25 '17 at 13:18
  • @Fis My question was not rethorical. What more are you asking for? What exactly does `async`/`await` not accomplish for you? – jib Jan 25 '17 at 13:22
  • @jfriend00 I never said it turns code into synchronous code. The OP specifically asked *not* to block the browser, so clearly they want other things to run. As to "magic" I think you're selling the aspirations of `async`/`await` short. – jib Jan 25 '17 at 13:28
  • @jib Does not stop execution (prevent returning from the function) until the async task completes. Take a look to Pointy's comment above, thats how it actually works. What is important: its not achievable in the sync way. I have to do it async with all its aspects. And it does not matter if i'll use callback, promises or new async/await what is actually syntax sugar for promises. The problem is the code will get more complicated. From nice three lines, about 30. – Fis Jan 25 '17 at 13:29
  • @jfriend00 - i don't need other tasks to run (except other asynchronous, will be painful if browser stop receiving data of transfer in progress ;) Just wanted avoid "Script does not respond" dialog and don't prevent user to click browser navs, close tab... Unfortunately, not possible – Fis Jan 25 '17 at 13:32
  • @Fis. Yes possible. My fiddle is 14 lines of code. Run it. It does not cause "Script does not respond", and satisfies every other practical thing you've asked for. I don't see any comment from Pointy. – jib Jan 25 '17 at 13:38
  • @jib - That's part of my point - other tasks will be able to run and you have to understand the consequences of that. async/await does not block execution of other Javascript and is not exactly the same as synchronous Javascript so you have to really understand how it works and what it does in order to use it wisely and safely. If, for example, you had a button in your web page, that button would be fully active while something was "awaiting". Sometimes that's exactly what you want - sometimes it's not what you want and you have to write additional code to get what you want. – jfriend00 Jan 25 '17 at 17:23
  • @jfriend00 OP just said two comments ago they expect other asynchronous tasks to run. We all agree [coroutines are bad](http://calculist.org/blog/2011/12/14/why-coroutines-wont-work-on-the-web/), hence the explicit `async`/`await` keywords. However, scaring people away from async/await, saying only experts should use it, would be a mistake IMHO. – jib Jan 25 '17 at 17:52
0

Simply, The Answer is NO.

Don't think in this way. think about backend performance rather than doing in client side.

Jameel Grand
  • 2,294
  • 16
  • 32
  • Can't be done on server in my case. Backend is dumb "file" server without computing power and scripting / coding possibilities. – Fis Jan 25 '17 at 11:10
  • And by the way, saving the bandwidth by reusing the same image through mutliple stylesheets... backend can't help Moreover, I don't like devs who says: move everything to backend, we can scale there... and write code like pigs. I am quiet old school guy from times when every memory saved byte helped a lot and every transferred byte costed a lot of money. Fortunately, we are in different times but it does not mean we should not optimize effectively and just say, lets scale. Especially when clients are going to be powerful enough to do some tasks there (of course, not a security stuff). – Fis Jan 25 '17 at 11:21
0

No, you can't do that even with 100% cpu usage or a blocked browser. Things have been precisely engineered to prevent you from doing that.

This makes all asynchronous code is poisonous, meaning all code that calls async functions (either promise or callback based) must be asynchronous itself.

About the terrible code: Promise chains and async/await for the rescue, they help you to write asynchronous code with much better syntax. Don't be afraid, use async/await especially if you already use a transpiler.

Tamas Hegedus
  • 28,755
  • 12
  • 63
  • 97
  • 1
    It does not mean the promise or sync/async will make it nicer than sync :D Many other tasks must be done in async compared to sync. – Fis Jan 25 '17 at 11:45
0

Is it possible, somehow, to call async function (such as timer / ajax call), basically common async task and synchronously wait until it ends without having 100%CPU usage and blocked browser?

No. You can't "synchronously wait" for an async result in Javascript. Because of it's single threaded and event-driven design, it just won't work that way.

As the majority of the other talk/comments about this question have been conceptual in nature, I thought I'd offer an answer about how you would practically solve your specific problem using an appropriate asynchronous design.

Javascript does not offer the ability to sleep while some time consuming activity finishes. So, you cannot design your updateCSS() function to have a purely synchronous interface. Instead, you will need to use asynchronous tools in Javascript to design an interface that would work for both the case where the resource is cached and when the resource must be fetched over the network.

The usual way to design for that is to design an async interface and, if the resource happens to be cached, you still serve it through the async interface. Then, you have one interface that the caller can use and that interface always works whether the resource is cached or not. It requires the caller to use an async interface.

Practically, you can design like this getCachedResource():

function getCachedResource(url) {
  //...
  if (cached) {
    // returns a promise who's resolved value is the resource
    return Promise.resolve(cached);
  } else {
    // returns a promise who's resolved value is the resource
    return doAjax(...);
  }
}

Here getCachedResource() always returns a promise, whether the value is cached or not. So, the caller always uses the promise interface like this:

getCachedResource(...).then(function(result) {
    // use the result here
});

If the result happens to be cached, the .then() handler will be called immediately (on the next tick). If the result is retrieved over the network, the .then() handler will be called as soon as the result is available. The caller doesn't need to worry about where or how the resource was retrieved - they have the one interface for getting it.

In the updateCSS() function, you're going to have to design for using the asynchronous interface in getCachedResource(). Because your current design uses a synchronous callback in css.replace(), you'll have to rethink that a bit to fit the async retrieval into there. Here's one approach for how to do that.

function updateCSS(url) {
   return getCachedResource(url).then(function(css) {
       // preflight the replacements we need to do so we know what to fetch
       var urls = [];
       css.replace(/regexp/gm, function(curUrl) {
           urls.push(curUrl);
       });
       // now load all the resources
       return Promise.all(urls.map(getCachedResource)).then(function(resources) {
           // now have all the resources, in proper order
           var index = 0;
           css = css.replace(/regexp/gm, function(curUrl) {
               return resources[index++];
           });
           // final resolved value is the fully loaded css
           return css;
       })
   });
}

The scheme here is to run a dummy .replace() that does not keep the result to generate the list of urls you need to load. Depending upon your regex, this could also be done with .match() or .exec(). Then, once you have the list of URLs to fetch, go get them all. When you have all of them, then you can run the .replace() for real because you have the resources available to use in the synchronous .replace() callback.

One could then use this like this:

updateCss(someCSSURL).then(function(css) {
    // use the CSS here
}).catch(function(err) {
    // process error here
});
jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • Thanks for a lot of work you did! Hopefully it will be great example for others. I'll do it in bit different way as I can't (don't want) to use promises :) – Fis Jan 26 '17 at 13:47
  • @Fis - Why not use promises? They are the modern way to manage async operations. – jfriend00 Jan 26 '17 at 14:34
  • Because polyfills and everything related to them (all the tool chain necessary to build the complete app such as gulp, webpack and others) are forbidden in my project at this time :) – Fis Jan 27 '17 at 16:06
  • btw, fortunately we are not pushed to use everything "modern" right? :) – Fis Jan 27 '17 at 16:08
  • @Fis - Of course not, but modern things are invented for a reason because they solve problems and make life easier. They are often "advances" in how we do things. I don't know why simply using promises has to drag in gulp and webpack. Yes, if you intend to support old browsers, you would need a polyfill (I would suggest bluebird), but that is one script tag from a CDN, not everything you're talking about. I could write the above code without promises, but it would take 5x as long and a lot more code and I'd be rewriting logic already solved by promises. – jfriend00 Jan 27 '17 at 17:11
  • I need to have everything on one place and trustworthy (so basically review the source of 3rd party libs then use them), so CDN is not a good idea. But I have solved problem with 3rd party libs aka polyfills already so i'll do async/await. – Fis Jan 28 '17 at 21:44
  • @Fis - If you have async/await support, then you already have Promise support (await uses promises) so it's odd you would ding a promise-based solution based on that. Are you transpiling your code to get async/await compatibility in a wide range of browsers? Or are you only targeting browsers that only support async/await already? Or are you not aware that some browsers don't support async/await which is an ES7 feature? – jfriend00 Jan 28 '17 at 22:48
  • Yes, thats correct. So currently I am transpilling TypeScript 2.1 to ES5 (without any other tool in toolchain - like babel) and using es6-promise polyfill for older browsers. Works fine in IE10+ and other standard browsers I am targetting on. I was asking this question just to be sure there is really not nicer way to do async, because even with promises/async/await the code is complicated compared to sync – Fis Jan 29 '17 at 23:33
  • And I also wanted to avoid 3rd party libs (like es6-promise polyfill) because of many reasons but in the end I changed my mind and i'll use/allow use of them. I'll let you know once I'll push the updated code to github so you can check it. But first i need to finish debugging as it is broken after rewriting from TS2.0 + callbacks to TS2.1 + promises + many other updates :) But its Visual Studio solution so maybe not interresting for you :) – Fis Jan 29 '17 at 23:34
  • So you can check it, mailny the resource manager contains await/async now, the rest is under development. https://github.com/atomsoftwarestudios/AjsDoc – Fis Feb 02 '17 at 10:20
0

I like all the other answers, and I understand why you cannot make an async task synchronous. I got brought here trying to figure out how to await for the FileReader object to finish reading so I can read multiple files with the same FileReader object.

Issues with personal problem that I overcame: The FileReader object does not return a promise, therefore you cannot await.

I made a recursive function that would run in a loop to do the first read, and check when the .loadend event hit, then move on to the next file. For those critics out there, yes this is likely bad practice, but it works.

To return to your problem, the ajax request is asynchronous and like my example, it does not return a promise and therefore you can't use an await. After digging a bit, I found a stackoverflow post that likely could solve both of our problems in a practical way:

How to await the ajax request?

You can make a function that will take the url and return the ajax call. This can be awaited and therefore you can use a .then(doSomethingAfter()) and all the cool stuff promises can do. I'll let you know how it works for the FileReader after I try it.

~~~~EDIT~~~~

here's my code:

function getFileResolve(reader, files, currentNum) {
    try {
        var file = files[currentNum];
        return reader.readAsText(file)
    } catch(err) {
        console.log(err)
    }
};

function main() {
    $('#imported-files').on('change', function() {
        var files = this.files,
            reader = new FileReader(),
            currentFileNum = 0;

        reader.onload = function(e) {
            console.log(e.target.result);
            currentFileNum++;

            getFileResolve(reader, files, currentFileNum);
        }
        
        getFileResolve(reader, files, currentFileNum);
    })
};

$(document).ready(main);

The ajax call can be returned and from there can be treated as a promise. The FileReader object has a onloaded event, as mentioned before and therefore can just call the next file for you and it is recursive on it's own. Hopefully this helps anyone out there. #promises_are_hard

lua_python_java
  • 359
  • 2
  • 8