Asynchronous programming in JavaScript without messy callbacks
Asked Answered
H

7

7

I want to turn an asynchronous function to the synchronous.

function fetch() {
  var result = 'snap!';
  $.getJSON("http://api.flickr.com/services/feeds/photos_public.gne?tags=cat&tagmode=any&format=json&jsoncallback=?", function messyCallback(data){
    result = data;
  });
  return result;
}

document.write(fetch());​

See in action

The result always will be 'snap!', because $.getJSON run after fetch() is done.

My first idea was:

function fetch() {
  var result = 'snap!';
  $.getJSON("http://api.flickr.com/services/feeds/photos_public.gne?tags=cat&tagmode=any&format=json&jsoncallback=?", function messyCallback(data){
    result = data;
  });
  while (true) {
    if (result != 'snap!') return result;
  }
}

It doesn't work and also blow off the browser.

I read about generators and iterators in JS 1.7, but I have no idea how to apply it to my problem.

This question is not really about jQuery. Instead of $.getJSON could be any another asynchronous function.

Hereford answered 8/2, 2010 at 15:59 Comment(4)
Possible (likely?) duplicate - see #1456370.Tuggle
Jeff, I don't think so. I use JSONP call, which always synchronous.Hereford
@NV: JSONP is only synchronous when parsed as part of the HTML document. If it is added to the page using the DOM, it's async.Burundi
Whoops, I meant "which always asynchronous".Hereford
B
9

See this question also: Halt JavaScript execution without locking up the browser

Doing exactly what you want doesn't work. You can't create synchronousness from asynchronousness (only the other way around!) in a single-threaded event-driven environment. You should embrace the async nature of the language, and develop your own consistent style around handling callbacks so that your code is readable/maintainable. You could, for instance, choose to never do any real work inside a callback closure, but simply call another top-level function/method for clarity.

Beora answered 8/2, 2010 at 16:3 Comment(0)
M
2

You want to roll your own $.getSyncJSON that uses $.ajax({async:false}). See: http://api.jquery.com/jQuery.ajax/.

I must advise you against this course of action, however. You can easily lock up the browser from all input, putting your UI at the mercy of the network. But if you know what you are doing and are sure of your use case, go for it.

Monumental answered 8/2, 2010 at 16:11 Comment(0)
C
2

Instead of writing helper methods like your fetch that return a value, make them accept another function, a "receiver", to pass their result to:

function fetch(receiver) {

    $.getJSON("blah...", function(data) {

        receiver(data);
    });
}

Obviously this is redundant because it's exactly how getJSON already works, but in a more realistic example the fetch function would process or filter the result somehow before passing it on.

Then instead of:

document.write(fetch());​

You'd do:

fetch(function(result) { document.write(result); });

Generators can be used to make asynchronous code a lot more linear in style. Each time you needed some asynchronous result, you'd yield a function to launch it, and the generator would resume when the result was available. There'd be a bit of management code keeping the generator going. But this isn't much help because generators are not standard across browsers.

If you're interested, here's a blog post about using generators to tidy up asynchronous code.

Crin answered 8/2, 2010 at 16:34 Comment(1)
I had a play with Mozilla generators and they seem excellent. Shame they aren't widely adopted elsewhere. See the link I've added to the end of the answer.Crin
S
2

There is an extension to the JavaScript language called StratifiedJS. It runs in every browser, and it allows you to do just that: handling asynchronous problems in a synchronous/linear way without freezing your browser.

You can enable Stratified JavaScript e.g. by including Oni Apollo in your webpage like:

<script src="http://code.onilabs.com/latest/oni-apollo.js"></script>
<script type="text/sjs"> your StratifiedJS code here </script>

And your code would look like:

function fetch() {  
  return require("http").jsonp(
    "http://api.flickr.com/services/feeds/photos_public.gne?" +
    "tags=cat&tagmode=any&format=json", {cbfield:"jsoncallback"});
}
document.write(fetch());​

Or if you really want to use jQuery in StratifiedJS:

require("jquery-binding").install();
function fetch() {  
  var url = "http://api.flickr.com/?format=json&...&jsoncallback=?"
  return $.$getJSON(url);
}
document.write(fetch());​

The docs are on http://onilabs.com/docs

Sedillo answered 23/9, 2010 at 10:33 Comment(0)
T
2

The TameJS library is designed to deal with this problem.

You might write something like (untested):

var result = 'snap!';
await {
    $.getJSON("http://api.flickr.com/services/feeds/photos_public.gne?tags=cat&tagmode=any&format=json&jsoncallback=?", defer(result));
}
return result;
Tanishatanitansy answered 5/8, 2011 at 18:38 Comment(0)
C
1

The lower-level $.ajax() function from jQuery has more options, including async: [true|false] (default is true).

Nevertheless, in most cases you should follow Ben's advice and "embrace the async nature of the language".

Comatulid answered 8/2, 2010 at 16:10 Comment(1)
Drew: Thanks for calling this out. It's true that JS has a small number of (confusingly) synchronous calls, XMLHttpRequest's get being one of them.Beora
V
0

I know this a little late but you can avoid callbacks with promises and await in async function

deliverResult = (options) => (
  new Promise( (resolve, reject) => {
    $.ajax(options).done(resolve).fail(reject);
  })
)

getResult = async () => {
  let options = {
    type: 'get', 
    url: 'http://yourUrl.com', 
    data: {param1: 'arg1'}
  }
  console.log('waiting ..... ');
  let result = await deliverResult(options);
  console.log('**..waiting ended..**');
  console.log(result);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<input type='button' onclick='getResult()' value='click me'/>
Vivi answered 30/12, 2018 at 12:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.