ES6 Incentive Templates for Exotic Control Flows

ES6 Promises are great. Until now, it has been pretty easy to customize my thinking callback idiom. Ive found that this naturally encourages more modular code and course error handling is much clearer.

But several times I came across situations that do not seem (?) To be such as they can be easily translated from nodebacks to Promises (and maybe this is the case, but maybe Im just blind to the answers). Because Promises are agnostic about the next operation (or even where it is), it seems pretty difficult to use Promises with APIs that not only accept callbacks, but also return them.

The most common example that comes to mind is the “callback made”. It shows up in things like database connections to mean “reverse connection to the pool, but I saw that it pops up in a lot of other places as well.

function getSomeStupidConnection(cb) { var conn = /* ... */; var iNeedToBeToldWhenIAmDone = function() { /* ... */ }; cb(conn, iNeedToBeToldWhenIAmDone); } getSomeStupidConnection(function(conn, done) { /* ... */ conn.doLotsOfStuff(function(soMuchStuff) { /* stuff! so much fun! */ /* okay conn go away I'm tired */ done(); }); }); 

Reversing a stream like this is obviously not what you want in your APIs to start with, but it is there and you cannot avoid it. With callbacks, you can transfer a “call of a later internal callback to the original” external Chime. This does not lead to a clean separation of problems, but the least fast and simple.

Is there a promise based approach suitable for such situations? A way of saying, 'Heres the value of permission - but when the chain is complete, also do this? I Suspect that there is nothing that fits perfectly with what I just described, because it is really impossible to say that the chain is “done”, but maybe I am missing some template that brings you closer to this without creating a mess ...


Edit: Based on the reviews, I realized that it is simply impossible to wrap such an API in true promises, because the promise you return will never be able to tell you anything about any subsequent Promises chains that are piggybacked on it. But you can fake it. The twist is that the result is rather fragile; he must assume that the only then that needs the join object is the one that immediately follows. The consumer of the promise will need to understand that his one-time connection, which is not obvious. Therefore, I actually do not recommend this in practice, but for the sake of curiosity there is a solution that hides done , behaving like (and ultimately becoming) a chain of promises:

 /* jshint node: true, esnext: true */ 'use strict'; // Assume this comes from an external library. It returns a connection and a // callback to signal that you are finished with the connection. function getConnectionExternal(cb) { let connection = 'Received connection.'; let done = () => console.log('Done was called.'); cb(null, connection, done); } // Our promisey wrapper for the above function getConnection() { let _done; let promise = new Promise((resolve, reject) => { getConnectionExternal((err, connection, done) => { if (err) return reject(err); _done = (val) => { done(); return val; }; resolve(connection); }); }); let _then = promise.then.bind(promise); promise.then = (handler) => _then(handler).then(_done, _done); return promise; } // Test it out getConnection() .then(connection => { console.log(connection); return new Promise((resolve, reject) => { setTimeout(() => { console.log('Finished using connection!'); resolve('This should be after connection closes.'); }, 200); }); }) .then(msg => console.log(msg)) .catch(err => console.error(err)); 

Console prints:

  • The resulting compound.
  • Finished using the connection!
  • Done was called up.
  • This should be after closing the connection.
+5
source share
4 answers

A way to say: “heres permission value”, but when the chain is complete, also do this?

No, native promises does not provide such a tool. I would go with a resource function that performs a return response that returns a call, callback does everything (in the chain) that needs to be done while the connection is open. Instead of passing an iNeedToBeTold callback, the resource manager function fulfills the promise and does what needs to be done when it resolves.

 function manageConnection(cb) { return getSomeConnection(…) // get the connections asynchronously - via a promise of course .then(function(conn) { function whenDone() { … // do what needs to be done return result; } var result = cb(conn); return result.then(whenDone, whenDone); }); } manageConnection(function(conn) { return conn.doLotsOfStuff(soMuch) .then(function(stuff) { /* stuff! so much fun! */ }); }).then(…) 
+3
source

The problem with the done () function is that people forget to call it, causing leaks.

I like Bergi's answer with the transferred callback because it is clean, but it is not a very “promise-y,” and it is still openly unsafe, for example. if people cling to promises that never allow the promise of a callback, then it rushes and flows.

This is a problem discussed in the browser APIs, and one template we are thinking about is returning

AutoClosingPromise

The AutoClosingPromise function acts like a promise, but does two things differently:

  • It "closes the ticket" (completed calls) after executing its .then ().

  • Also, when he assumes another promise, if he sees another AutoClosingPromise returned from his .then (), then he forwards it (passes this promising ticket - another ticket) to AutoClosingPromise, which he returned from his own .then () )

The first part means that the API can return AutoClosingPromise with a “ticket” that contains an open resource (for example, an open counter), and be sure that the ticket will be closed after the first .then () function returns.

The second part allows the caller to make additional asynchronous calls to the API from the direct .then () function, allowing the API to keep the resource open as long as tickets overlap in time.

A feature of this is that resources are not served through regular promises, only with AutoClosing, avoiding the risk of leaks. For instance:

 var lock = new ExampleLock(); lock.access("foo") .then(() => lock.set("foo1")) .then(() => lock.set("foo2")) .then(() => lock.set("foo3")) .then(() => {}) .then(() => lock.set("foo4")) .catch(failed); 

will expand the resource (lock) to the first three, but not the fourth:

 setting foo1 [LOCKED] setting foo2 [LOCKED] setting foo3 [LOCKED] setting foo4 [UNLOCKED] 

Here is the code:

 function AutoClosingPromise(ticket, p) { this.pending = true; this.ticket = ticket; var close = result => { this.pending = false; if (this.ticket) { this.ticket.close(); if (result && result.handoffTicket && this.returnedThenPromise) { // callback returned an AutoClosingPromise! Forward its ticket this.returnedThenPromise.takeTicket(result.handoffTicket()); } } return result; }; this.p = p.then(v => close(this.success && this.success(v)), r => close(this.failure && this.failure(r))); } AutoClosingPromise.prototype = { then: function(success, failure) { if (this.pending && !this.success && !this.failure) { this.success = success; this.failure = failure; this.returnedThenPromise = new AutoClosingPromise(null, this.p); return this.returnedThenPromise; } else { return this.p.then(success, failure); } }, takeTicket: function(ticket) { this.ticket = ticket; }, handoffTicket: function() { var ticket = this.ticket; this.ticket = null; return ticket; } }; 

and a script: http://jsfiddle.net/jib1/w0ufvahL (you need a browser that understands the functions of es6 arrows, for example Firefox, for example, not Chrome).

Since the API manages all the asynchronous calls that issue tickets, this should be enough of a leak. For instance. even if the caller ignores the promise returned from the API in full, close is still called.

Please note that this is a fairly new idea, not a proven design, so if you use it, let me know how it works .; -)

+4
source

When developing a Bergi solution, this is called a deletion pattern. It exists in many forms in many languages ​​- with in Python, using in C # and try(){ with a resource in Java. Some languages ​​process a resource in areas this way initially through destructors such as C #.

The general idea is to encapsulate the lifetime of a value. In your case, a database connection. This is much more complicated than calling done in the callback, since it is much easier to forget to call done , which leaves an open connection and a resource leak. Synchronously, it would look like this:

 function scope(cb){ try{ var conn = getConnection(...); return cb(conn); } finally { conn.release(); } } 

The promises version is not too different:

 function conn(data){ var _connection; return getConnection().then(function(connection){ _connection = connection; // keep a reference return data(_connection); // pass it to the function }).then(function(val){ // release and forward _connection.release(); // if release is async - chain return val; }, function(err){ _connection.release(); throw err; // forward error }); }); 

What will use:

 conn(function(db){ return db.query("SELECT * FROM ..."); }).then(function(result){ // handle result // connection is released here }); 
+3
source

I am not 100% sure what you get, but maybe this is what you are after? Essentially nested promises ...

 let connection = function(){ return new Promise(function(resolve, reject){ window.setTimeout(resolve("There"), 5000); }) } let connectionManager = function(){ return connection().then(function(value){ console.log("Hello"); return value; }); } connectionManager().then(function(value){ console.log(value); }); 

Here he is at the Babel REPL .

If you are trying to wrap existing asynchronous functions with promises, this example may also help: http://www.2ality.com/2014/10/es6-promises-api.html#example%3A_promisifying_xmlhttprequest

Depending on the required nesting requirements, you can resolve the promise using the promise object; feel free to comment if you need clarification :)

0
source

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


All Articles