--harmony
or --harmony-generators
flag.yield
operator, and transfer the result in or out when they are resumed or suspended. In this way, we can make a “pause” when the function waits for the result of another function without passing a callback into it.function*
expressions: function* foo(x) { yield x + 1; var y = yield null; return x + y; }
var gen = foo(5); gen.next(); // { value: 6, done: false } gen.next(); // { value: null, done: false } gen.send(8); // { value: 13, done: true }
yield
allowed in all expressions.next
or send
to resume the generator. send
used when you want to send a value back to it. gen.next()
equivalent to gen.send(null)
. There is also a gen.throw
that throws an exception inside the generator.value
and done
. Thanks to done
it becomes clear when the generator is finished, either with a return
, or a simple end of the function, instead of the inconvenient exception StopIteration
, which was in the old API. var suspend = require('suspend'), fs = require('fs'); suspend(function*(resume) { var data = yield fs.readFile(__filename, 'utf8', resume); if(data[0]) { throw data[0]; } console.log(data[1]); })();
suspend
function passes your generator inside a normal function that starts the generator. It passes the resume
function to the generator, the resume
function must be used as a callback for all asynchronous calls, it resumes the generator with arguments containing the error and value flags.resume
and generator are interesting, but there are some drawbacks. First, the backward array of two elements is inconvenient, even with destructuring ( var [err, res] = yield foo(resume)
). I would like to return only the value, and throw the error as an exception, if any. The library actually supports this, but as an option, I think this should be the default.callback
and call it at the end of the function, as is usually done in the node.suspend
along with one of them, but I would prefer to see a flow control library that includes generator support.client
is an instance of redis): client.hmset('blog::post', { date: '20130605', title: 'g3n3rat0rs r0ck', tags: 'js,node' }, function(err, res) { if(err) throw err; client.hgetall('blog::post', function(err, post) { if(err) throw err; var tags = post.tags.split(','); var posts = []; tags.forEach(function(tag) { client.hgetall('post::tag::' + tag, function(err, taggedPost) { if(err) throw err; posts.push(taggedPost); if(posts.length == tags.length) { // - post taggedPosts client.quit(); } }); }); }); });
var db = { get: Q.nbind(client.get, client), set: Q.nbind(client.set, client), hmset: Q.nbind(client.hmset, client), hgetall: Q.nbind(client.hgetall, client) }; db.hmset('blog::post', { date: '20130605', title: 'g3n3rat0rs r0ck', tags: 'js,node' }).then(function() { return db.hgetall('blog::post'); }).then(function(post) { var tags = post.tags.split(','); return Q.all(tags.map(function(tag) { return db.hgetall('blog::tag::' + tag); })).then(function(taggedPosts) { // - post taggedPosts client.quit(); }); }).done();
then
and wait for the result of the asynchronous operations. Much more detail is explained in the promises / A + specification .Q
implements several additional methods, such as all
, it takes an array of promises and waits until each one of them completes. In addition, there is done
, which says that your asynchronous process has ended and any unhandled errors should be thrown. According to the promises / A + specification, all exceptions should be converted to errors and passed to the error handler. Thus, you can be sure that all errors will be thrown if they do not have a handler. (If something is not clear, please read this article from Dominic.)post
, and then to taggedPosts
. It feels callback-style code, it's annoying. Q.async(function*() { yield db.hmset('blog::post', { date: '20130605', title: 'g3n3rat0rs r0ck', tags: 'js,node' }); var post = yield db.hgetall('blog::post'); var tags = post.tags.split(','); var taggedPosts = yield Q.all(tags.map(function(tag) { return db.hgetall('blog::tag::' + tag); })); // - post taggedPosts client.quit(); })().done();
Q.async
takes a generator and returns a function that controls it, like the suspend library. However, the key difference here is that the generator gives (yields) promises. Q accepts each promise and connects the generator with it, does resume when the promise is completed, and sends the result back.resume
- promises to handle it completely, and we get the advantage of promises .Q.all
, which runs several asynchronous operations in parallel. Thus, you can easily combine similar Q promises and implicit promises in generators to create complex execution threads that look very clean.post
and taggedPosts
remain in the same scope, we no longer care about breaking the chain of scope into then
, which is incredibly pleasing.async
generator is a promise, with no exceptions. You can manage errors using error callback: someGenerator().then(null, function(err) { ... })
.gen.throw
will be thrown by an exception from the point where the generator was suspended. This means that you can use try/catch
to handle errors in the generator: Q.async(function*() { try { var post = yield db.hgetall('blog::post'); var tags = post.tags.split(','); var taggedPosts = yield Q.all(tags.map(function(tag) { return db.hgetall('blog::tag::' + tag); })); // - post taggedPosts } catch(e) { console.log(e); } client.quit(); })();
db.hgetall
call will be handled in the catch
handler, even if it is an error in a deep promise inside Q.all
. Without try/catch
exception will be passed to the caller's promise error handler (if there is no caller, the error will be suppressed).try
block is being executed will be passed to catch
. You can use finally
to create confident “cleanup” code at startup even for an error, without the presence of an error handler.done
whenever you use promises — by default, you can get thrown errors instead of quiet ignoring, which happens too often with asynchronous code. The way to use Q.async
, as a rule, looks like this: var getTaggedPosts = Q.async(function*() { var post = yield db.hgetall('blog::post'); var tags = post.tags.split(','); return Q.all(tags.map(function(tag) { return db.hget('blog::tag::' + tag); })); });
Q.async(function*() { var tagged = yield getTaggedPosts(); // - tagged })().done();
done
method is guaranteed to throw an error for any unhandled error as an exception. I believe that this approach is common, but you need to call an extra method. getTaggedPosts
will be used by promise-generating functions. The code above is simply a top level code that is filled with promises. Q.spawn(function*() { var tagged = yield getTaggedPosts(); // - tagged });
spawn
takes a generator, immediately starts it, and automatically forwards all unprocessed errors. This is exactly equivalent to Q.done(Q.async(function*() { ... })())
. var getKey = Q.async(function*(key) { var x = yield r.get(dbkey(key)); return x && parseInt(x, 10); });
function getKey(key) { return r.get(dbkey(key)).then(function(x) { return x && parseInt(x, 10); }); }
yield Q.all(keys.map(Q.async(function*(dateKey) { var date = yield lookupDate(dateKey); obj[date] = yield getPosts(date); })));
spawnMap
that performs Q.all(arr.map(Q.async(...)))
for you. yield spawnMap(keys, function*(dateKey) { var date = yield lookupDate(dateKey); obj[date] = yield getPosts(date); })));
map
method from the async library.Q.async
function and make it forward all errors. This happens with normal callbacks from different libraries, such as express: app.get('/url', function() { ... })
.Q.async
function, because then all the errors will be quietly suppressed, I also cannot use the Q.spawn
because it is not executed immediately. Perhaps something like asyncCallback
would be good: function asyncCallback(gen) { return function() { return Q.async(gen).apply(null, arguments).done(); }; } app.get('/project/:name', asyncCallback(function*(req, res) { var counts = yield db.getCounts(req.params.name); var post = yield db.recentPost(); res.render('project.html', { counts: counts, post: post }); }));
Source: https://habr.com/ru/post/182620/
All Articles