⬆️ ⬇️

Asynchronous synchrony. Jsdeferred

Recently, several articles about working with asynchronous calls ( after all asynchronous calls , Synchronizing asynchronous calls. WaitSync ) have appeared on Habré . But upon closer examination, the scope of their application is rather narrow since these methods do not solve all the problems.

But first we will try to identify these very problems that we face when working with asynchronous calls.



And for the experiences we will choose an example * in which we need to request a user profile, new messages, and only then display the page. Using synchronous execution, the solution would be:

var profile = get_profile(user); var msgs = get_msgs(user); render_page({'profile' : profile, 'msgs' : msgs}); 


* All examples are synthetic, they miss a number of points for the sake of focusing on the essence



Problems:



1. Big code nesting



The asynchronous version of our example looks like this:

 var params = {}; get_profile(user, function(profile){ params['profile'] = profile; get_msgs(user, function(msgs){ params['msgs'] = msgs; render_page(params); }) }) 


With a large number of nested requests, such a record becomes poorly readable and difficult to debug.



2. Parallel calls



In our example, all calls go through sequentially, but they are all tied only to the user argument and do not depend on each other, so I would like to speed up page generation in order to execute them in parallel.

')

Usually do this:

 var params = { 'profile' : null, 'msgs' : null } render_page(){ if (params['profile'] && params['msgs'] !== null){ do_render_page(); } } get_profile(user, function(data){ params['profile'] = data; render_page(); }) get_msgs(user, function(data){ params['msgs'] = data; render_page(); }) 


This problem is described in After All Asynchronous Calls and Synchronizing Asynchronous Calls. WaitSync .



Using the method from the topics above, the solution would look like this:

 var process = render_page.after('profile', 'new_msgs'); get_profile(user, process.profile); get_msgs(user, process.new_msgs ); 




3. Error handling



Suppose we need to handle errors or exceptions that occurred during the execution of get_profile () or get_msgs ()



For synchronous code, everything is quite simple:

 try{ var msgs = get_msgs(user); var profile = get_profile(user); if (profile){ render_page({'profile' : profile, 'msgs' : msgs}); }else{ redirect_to_login_page(); } }catch(e){ show_error_msg(e); } 




For asynchronous calls, errors can be passed as a parameter to the callback or use a separate callback. Exceptions that may accidentally / specifically occur inside get_profile () or get_msgs () are so easy for us to catch outside.



4. Extensibility



This problem occurs as a result of the first three.

Suppose we wanted to consistently get another list of recent comments, recent read topics, rating. Then the example from the first paragraph will turn into a scary monster.

 var params = {}; get_profile(user, function(profile){ params['profile'] = profile; get_msgs(user, function(msgs){ params['msgs'] = msgs; get_last_comments(user, function(comments){ params['comments'] = comments; get_last_readed_topics(user, function(topics){ params['topics'] = topics; get_rating(user, function(rating){ params['rating'] = rating; render_page(params) }) }) }) }) }) 


If we add a callback to error handling, then programmers are likely to curse us, who may have to understand your code.



We are in a hurry to help us ... JSDeferred



And now I want to introduce those who are not yet familiar with one of the implementations of the Deferred mechanism, namely JSDeferred . This library allows you to work with asynchronous calls as with synchronous. What the solution to our four problems will now look like:



1. Large code nesting (solution)



Callbacks are replaced by chains. The result of executing a chain link is passed to the next link by an argument.

 var params = {}; next(function(){ return get_profile(user) }). next(function(profile){ params['profile'] = profile }). next(function(){ return get_msgs(user) }). next(function(msgs){ params['msgs'] = msgs; render_page(params); }) 




2. Parallel calls (solution)



The next link will be called only after all queries in parallel return results. The argument will be an array of parallel execution results.

 parallel([ get_profile(user), get_msgs(user) ]). next(function(params){ render_page({'profile' : params[0], 'msgs' : params[1]}) //    // render_page.apply(this, params); }) 




3. Error handling (solution)



When an exception occurs in a link, the next error link in order of motion will be called up. The parameter passes a message thrown through throw;

 var params = {}; next(function(){ return get_profile(user) }). error(function(e){ redirect_to_login_page(); }). next(function(profile){ params['profile'] = profile }). next(function(){ return get_msgs(user) }). error(function(e){ show_error_msg(e); }). next(function(msgs){ params['msgs'] = msgs; render_page(params); }) 




4. Extensibility (solution)



Everything should be pretty clear here to add a new handler step - just add a new element to the chain.



Nuance



We considered the solution of the basic problems, and now about one nuance. Asynchronous functions / methods need to be prepared in a special way:

1. They must return a Deferred object.

2. To move further along the chain, call the call () method of the Deferred object.

3. To generate an error, call the fail () Deferred method.

A complete list of methods can be found in the documentation .



Those. the modified function using XmlHttpRequest will look like this:

 function http_get(url) { var d = Deferred(); var xhr = new XmlHttpRequest(); xhr.open("GET", url, true); xhr.onreadystatechange = function() { if (xhr.readyState != 4) return; if (xhr.status==200){ d.call(xhr.responseText); } else { d.fail(xhr.statusText); } } xhr.send(null); return d; } 




Conclusion



For single AJAX requests, the Deferred utility is rather dubious, but if you use many interconnected asynchronous calls, or the project has the prospect of growing and becoming more complex, then it makes sense to pay attention to Deferred. This is a very powerful mechanism by which you can build large chains with combined parallel / sequential execution of links, error handling, and this is all with readable code.



Recommended literature



1. JSDeferred on github

2. JSDeferred project page

3. Nested asynchronous calls. Deferred object in detail

4. dojo.Deferred

5. jQuery Deferred / JSDeferred

Source: https://habr.com/ru/post/108575/



All Articles