Reputation: 1671
This problem should be clear: I want to write into a log file using a statement like the following. You can assume that this statement is inside a click handler for a button. There are two questions embedded in this pseudocode.
pLogInfo("local info").then(pauseUntilSettled).catch(err); // This is my goal
Here are my library functions (each returns a Promise):
// Get info asynchronously from a server (simple, should work)
function pGetServerInfo()
{
// then value: "server info"
} // pGetServerInfo
// Write into a file asynchronously (simple, should work)
function pWriteFile(path,string)
{
// no then value
} // pWriteFile
// Write formatted info into a log file asynchronously
function pLogInfo(localInfo)
{
pGetServerInfo().then(p2);
} // pLogInfo
function p2(serverInfo)
{
// Should write "local info, server info"
// But where is the local info? It got lost.
return pWriteFile('log',localInfo+','+serverInfo);
} // p2
Usage:
pLogInfo("local info").then(pauseUntilSettled).catch(err);
function pauseUntilSettled()
{
// How to wait before returning from
// the button click event handler?
} // pauseUntilSettled
ADDED 8/27/19:
Several possible solutions to this common problem occur to me:
Attach an object to one of the top functions in your chain (p.data={}). You can store any arguments or callbacks you wish in the object and reference them in any asynchronous 'then' code. This works because the function that is the parent of the object has global scope. It can fail if you fire another top-level Promise of the same thread while an existing Promise is not yet settled, because the new execution thread will share and overwrite the object. I have successfully used this approach, a variant of the above global formulation, but it is clearly unsafe.
Create a closure function to propagate the asynchronous thread. A closure function contains a snapshot of its arguments and referenced global variables. I have not yet gotten this idea to work, but it seems reasonable.
Create a new Promise, either as part of the thread of Promises or as a separate helper, that calls its resolve function with an object instead of a single value. Use that object to propagate more than one value to each "then" function. I have not gotten this idea to work either.
I hope these ideas inspires someone (including myself) to come up with a solution, because it is a very common problem that is not often discussed.
Upvotes: 0
Views: 62
Reputation: 1671
Solution:
You can put intermediate values in scope in any later 'then' function explicitly, by using 'bind'. It is a nice solution that doesn't require changing how Promises work, and only requires a line or two of code to propagate the values just like errors are already propagated.
Here is a complete example:
// Get info asynchronously from a server
function pGetServerInfo()
{
// then value: "server info"
} // pGetServerInfo
// Write into a file asynchronously
function pWriteFile(path,string)
{
// no then value
} // pWriteFile
// The heart of the solution: Write formatted info into a log file asynchronously,
// using the pGetServerInfo and pWriteFile operations
function pLogInfo(localInfo)
{
var scope={localInfo:localInfo}; // Create an explicit scope object
var thenFunc=p2.bind(scope); // Create a temporary function with this scope
return (pGetServerInfo().then(thenFunc)); // Do the next 'then' in the chain
} // pLogInfo
// Scope of this 'then' function is {localInfo:localInfo}
function p2(serverInfo)
{
// Do the final 'then' in the chain: Writes "local info, server info"
return pWriteFile('log',this.localInfo+','+serverInfo);
} // p2
This solution can be invoked as follows:
pLogInfo("local info").then().catch(err);
(Note: a more complex and complete version of this solution has been tested, but not this example version, so it could have a bug as written.)
Upvotes: 0
Reputation: 27
Satpal gave a great answer for promises. Another option is to use the RXJS library and utilize observables which are built on top of Promises. They have a next(), error() and complete() block where code will only execute once values are received in the stream. If you want to wait on multiple services to respond you can combine the streams together in multiple ways.
I know this is not the exact answer you are looking for but it is a very useful library. Here is the documentation. https://rxjs-dev.firebaseapp.com/api/index/class/Observable
Upvotes: 2