JavaScript
. Keeping in mind the order of execution in this language is a bit difficult (this is the case that is called “ Callback Hell ” or “ The Pyramid of Doom ”), if you have dealt with synchronous programming before. My usual answer was “you have to deal with it somehow.” After all, do we expect all programming languages to look and feel the same? Of course not.ECMAScript 6
, which describes the generators - the possibility of a language that will completely change our way of writing both server-side and client-side JavaScript
. With the help of generators, we can turn nested callbacks into a similar synchronous code without blocking our only event loop
. setTimeout(function(){ _get("/something.ajax?greeting", function(err, greeting) { if (err) { console.log(err); throw err; } _get("/else.ajax?who&greeting="+greeting, function(err, who) { if (err) { console.log(err); throw err; } console.log(greeting+" "+who); }); }); }, 1000);
sync(function* (resume) { try (e) { yield setTimeout(resume, 1000); var greeting = yield _get('/something.ajax?greeting', resume) var who = yield _get('/else.ajax?who&greeting=' + greeting, resume) console.log(greeting + ' ' + who) } catch (e) { console.log(e); throw e; } });
Chrome Canary 33.0.1716.0
. Examples, except for those with XHR
, should work in Node.js
with the --harmony
flag (from version 0.11, approx. Transl. ). The generator implementation proposed in JavaScript 1.7+
does not follow ECMAScript 6
draft — so you will have to make some changes to make the examples work in Firefox
. If you want to run these examples in Canary
, you can run them in the same way as here.ES6
generators and what they allow you to do.ECMAScript 6
draft, generators are “first-class coroutines that are objects that encapsulate pending execution contexts.” Simply put, generators are functions that can stop their execution (using the yield
keyword) and continue their execution from the same place after calling their next
method. JavaScript
still performs only one task at the same time, but it is now able to pause execution in the middle of the body of a generator function and switch the context to something else. Generators do not allow parallel execution of code and they do not know how to handle threads. function* fibonacci() { var a = 0, b = 1, c = 0; while (true) { yield a; c = a; a = b; b = c + b; } } function run() { var seq = fibonacci(); console.log(seq.next().value); // 0 console.log(seq.next().value); // 1 console.log(seq.next().value); // 1 console.log(seq.next().value); // 2 console.log(seq.next().value); // 3 console.log(seq.next().value); // 5 } run();
run
function initializes the Fibonacci number generator (it is described by the special syntax funtion*
). Unlike the usual function, this call does not start the execution of its body, but returns a new object - the generator.run
function calls the generator method next
(a synchronous operation), the code is executed until it reaches the yield
.yield
stops the generator and returns the result to the outside. The operations following yield
have not been performed at this point. The value (operand a
for yield
) will be available outside through the value
property of the result of the execution.next
method, the execution of the code continues from where it stopped at the previous yield
.while
. No, it will be executed inside the loop until someone calls its next
method.yield
will not be executed until the generator continues. You can also pass an argument to the generator, which will be substituted instead of the yield
on which the previous execution of the generator was interrupted. function* powGenerator() { var result = Math.pow(yield "a", yield "b"); return result; } var g = powGenerator(); console.log(g.next().value); // "a", from the first yield console.log(g.next(10).value); // "b", from the second console.log(g.next(2).value); // 100, the result
"a"
as the value
property of the execution result. Then we continue execution by passing a value of 10
to the generator. Use the substitution to demonstrate what happens: function* powGenerator() { var result = Math.pow(----10----, yield "b"); return result; }
yield
and again suspends its execution. The value "b"
will be available in the returned object. Finally, we continue execution again, passing in argument 2
. Substitution again: function* powGenerator() { var result = Math.pow(----10----, ----2----); return result; }
pow
method is called, and the generator returns the value stored in the result
variable.Ajax
JavaScript
code. As it turns out, we can take some ideas from previous examples.sync
function. It creates a generator, passing it the resume
function and calls the next
method on it to start its execution. When the generator needs an asynchronous call, it uses resume
as a callback and performs a yield
. When an asynchronous call performs resume
, it calls the next
method, continuing the execution of the generator and passing the result of the asynchronous call to it. // ************** // framework code function sync(gen) { var iterable, resume; resume = function(err, retVal) { if (err) iterable.raise(err); iterable.next(retVal); // resume! }; iterable = gen(resume); iterable.next(); } function _get(url, callback) { var x = new XMLHttpRequest(); x.onreadystatechange = function() { if (x.readyState == 4) { callback(null, x.responseText); } }; x.open("GET", url); x.send(); } // **************** // application code sync(function* (resume) { log('foo'); var resp = yield _get("blix.txt", resume); // suspend! log(resp); }); log('bar'); // not part of our generator function's body
event loop
; we stop the generator and continue to execute the code located further after the next
call. The future callback, which will be called on another tick, will continue our generator, passing it the desired value. try { firstAsync(function(err, a) { if (err) { console.log(err); throw err; } secondAsync(function(err, b) { if (err) { console.log(err); throw err; } thirdAsync(function(err, c) { if (err) { console.log(err); throw err; } callback(a, b, c); }); }); }); } catch (e) { console.log(e); }
catch
will never be executed due to the fact that the execution of the callback is part of a completely different callstack, in another tick of the event loop
. Exception handling must be located inside the callback function itself. You can implement a higher order function to get rid of some duplicate error checks and remove some attachments using a library like async
. If you follow the Node.js
convention about the error as the first argument, you can write a common handler that will return all errors back to the generator: function sync(gen) { var iterable, resume; resume = function(err, retVal) { if (err) iterable.raise(err); // raise! iterable.next(retVal); }; iterable = gen(resume); iterable.next(); } sync(function* (resume) { try { var x = firstAsync(resume); var y = secondAsync(resume); var z = thirdAsync(resume); // … do something with your data } catch (e) { console.log(e); // will catch errors from any of the three calls } });
catch
. And the exception that occurred in any of the three functions will not allow subsequent functions to be executed. Very good.genny
like genny
and gen-run
give this API: they simply perform a number of asynchronous operations before continuing the execution of the generator. Example using genny
: genny.run(function* (resume) { _get("test1.txt", resume()); _get("test2.txt", resume()); var res1 = yield resume, res2 = yield resume; // step 1 var res3 = yield _get("test3.txt", resume()); // step 2 console.log(res1 + res2); });
JavaScript
for a long time. But now with the generators in the browser ( Firefox
with JavaScript 1.7
and Chrome Canary
few months ago) everything is changing. New execution control constructs make it possible to use a completely new programming style, one that can compete with the traditional nested callback style. It remains to wait for the ECMAScript 6
standard to be implemented in tomorrow's JavaScript
engines.Source: https://habr.com/ru/post/210330/
All Articles