I'm trying to learn a little about Node and asynchronous programming. I read about Promises and have made an attempt at using them in a small project that copies posts for a user from Service A to Service B. I am having some trouble understanding how best to pass state between Promises
The project is written for NodeJS using the Promise library
A simple definition of my current problem is:
This is some pseudo code that illustrates how I have chained the Promises together.
Promise.from('service_A_username')
.then(getServiceAUserIdForUsername)
.then(getServiceAPostsForUserId)
.then(function(serviceAPosts) {
// but what? store globally for access later?
doSomethingWith(serviceAPosts);
return Promise.from('service_B_username');
})
.then(getServiceBUserIdForUsername)
.then(getServiceBPostsForUserId)
.done(function(serviceBPosts) {
// how do we interact with Service A posts?
doSomethingThatInvolvesServiceAPostsWith(serviceBPosts);
});
There are a couple of things that I have thought about doing:
Are there any other options, and what approach is recommended?
A Promise is in one of these states: pending: initial state, neither fulfilled nor rejected. fulfilled: meaning that the operation was completed successfully. rejected: meaning that the operation failed.
JavaScript Promise Chaining Promises are useful when you have to handle more than one asynchronous task, one after another. For that, we use promise chaining. You can perform an operation after a promise is resolved using methods then() , catch() and finally() .
"Producing code" is code that can take some time. "Consuming code" is code that must wait for the result. A Promise is a JavaScript object that links producing code and consuming code.
Promises in JavaScript There are 3 states of the Promise object: Pending: Initial State, before the Promise succeeds or fails. Resolved: Completed Promise. Rejected: Failed Promise.
First of all good question. This is something we (at least I) deal with with promises often. It's also a place where promises really shine over callbacks in my opinion.
What's going on here basically is that you really want two things that your library doesn't have:
.spread
that takes a promise that returns an array and changes it from an array parameter to parameters. This allows cutting things like .then(result) { var postsA = result[0], postsB = result[1];
into .spread(postsA,postsB
.
.map
that takes an array of promises and maps each promise in an array to another promise - it's like .then
but for each value of an array.
There are two options, either use an implementation that already uses them like Bluebird which I recommend since it is vastly superior to the alternatives right now (faster, better stack traces, better support, stronger feature set) OR you can implement them.
Since this is an answer and not a library recommendation, let's do that:
Let's start with spreading, this is relatively easy - all it means is calling Function#apply
which spreads an array into varargs. Here is a sample implementation I stole from myself:
if (!Promise.prototype.spread) {
Promise.prototype.spread = function (fn) {
return this.then(function (args) {
//this is always undefined in A+ complaint, but just in case
return fn.apply(this, args);
});
};
}
Next, let's do mapping. .map
on promises is basically just Array mapping with a then:
if(!Promise.prototype.map){
Promise.prototype.map = function (mapper) {
return this.then(function(arr){
mapping = arr.map(mapper); // map each value
return Promise.all(mapping); // wait for all mappings to complete
});
}
}
For convenience, we can introduce a static counterpart of .map
to start chains:
Promise.map = function(arr,mapping){
return Promise.resolve(arr).map(mapping);
};
Now, we can write your code like we actually want to:
var names = ["usernameA","usernameB"]; // can scale to arbitrarily long.
Promise.map(names, getUsername).map(getPosts).spread(function(postsA,postsB){
// work with postsA,postsB and whatever
});
Which is the syntax we really wanted all along. No code repetition, it's DRY, concise and clear, the beauty of promises.
Note that this doesn't scratch the surface of what Bluebird does - for example, Bluebird will detect it's a map chain and will 'push' functions on to the second request without the first one even finishing, so the getUsername
for the first user won't wait to the second user but will actually call getPosts
if that's quicker, so in this case it's as fast as your own gist version while clearer imo.
However, it is working, and is nice.
Barebones A+ implementations are more for interoperability between promise libraries and are supposed to be a 'base line'. They're useful when designing specific platform small APIs - IMO almost never. A solid library like Bluebird could significantly reduce your code. The Promise library you're using, even says in their documentation:
It is designed to get the basics spot on correct, so that you can build extended promise implementations on top of it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With