New to promises; consider the case where there is promiseA() and promiseB(a) which depends on the first result, and I want to collect results from both and perform a third action doSomething(a, b):
Style A (closure/nesting)
promiseA().then(function (resultA) {
return (promiseB(resultA).then(function (resultB) {
doSomething(resultA, resultB);
}));
});
Style B (return value/chaining)
promiseA().then(function (resultA) {
return Promise.all([resultA, promiseB(resultA)]);
}).spread(function (resultA, resultB) {
doSomething(resultA, resultB);
});
As far as I can tell, these are equivalent:
Same sequencing constraint between promiseA and promiseB
Final promise returns undefined
Final promise is rejected if promiseA or promiseB are rejected, or doSomething throws.
As a matter of style, Style B reduces indentation (pyramid of doom).
However, Style B is more difficult to refactor. If I need to introduce an intermediate promiseA2(a) and doSomething(a, a2, b), I need to modify 3 lines (Promise.all, spread, doSomething), which can lead to mistakes (accidental swapping etc), while with Style A I only to modify 1 line (doSomething) and the variable name makes it clear which result it is. In large projects, this may be significant.
Are there other non-functional trade-offs between the two styles? More/less memory allocation in one vs the other? More/fewer turns around the event loop? Better/worse stack traces on exceptions?
I think the non-functional trade-offs between the two methods are not so important: the second method has some overhead in creating the array, and spreading the corresponding results, and it will create one more promise. However in an asynchronous flow, all this is negligible in my opinion.
Your major concern seems to be the ease of refactoring.
For that I would suggest to use an array of functions, and reduce over it:
[promiseA, promiseB, doSomething].reduce( (prom, f) =>
prom.then( (res = []) => ( f(...res) || prom).then( [].concat.bind(res) ) )
, Promise.resolve() );
// Sample functions
function wait(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function promiseA() {
console.log('promiseA()');
return wait(500).then(_ => 13);
}
function promiseB(a) {
console.log('promiseB(' + a + ')');
return wait(500).then(_ => a + 2);
}
function doSomething(a, b) {
console.log('doSomething(' + a + ',' + b + ')');
}
The idea is that the next function in the then callback chain gets all of the previous results passed as arguments. So if you want to inject a promise-returning function in the chain, it is a matter of inserting it in the array. Still, you would need to pay attention to the arguments that are passed along: they are cumulative in this solution, so that doSomething is not an exception to the rule.
If on the other hand, you only want doSomething to get all results, and only pass the most recent result to each of the intermediate functions, then the code would look like this:
[promiseA, promiseB].reduce( (prom, f) =>
prom.then( (res = []) => f(...res.slice(-1)).then( [].concat.bind(res) ) )
, Promise.resolve() ).then(res => doSomething(...res));
function wait(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function promiseA() {
console.log('promiseA()');
return wait(100).then(_ => 13);
}
function promiseB(a) {
console.log('promiseB(' + a + ')');
return wait(100).then(_ => a + 2);
}
function doSomething(a, b) {
console.log('doSomething(' + a + ',' + b + ')');
}
Related
I've written a library that is completely separate from Promise API but achieves similar goals. It uses window.requestAnimationFrame and falls back to setTimeout and has nothing common with Promises. In fact you can run it on ie9 - 10 or a machine from 2009 etc. Here is the source code
How is it possible that the below code works, and the 2nd promise gets the value (v + 3) correctly into the 3rd promise after 10 second delay?? Because rafx.async... returns a custom proprietary object.
const x = Promise.resolve()
.then(() => console.log("2nd promise"))
.then(() => {
//600 frames === 10 secs
return rafx.async(6)
.skipFrames(600)
.then(v => v + 1)
.then(v => v + 3);
});
console.log(x instanceof Promise);
x.then(v => console.log("3rd promise", v));
<script src="https://cdn.jsdelivr.net/npm/rafx"></script>
The expectation would be that the v at x.then(v... would equal to whatever custom object returned from the custom then.
Here is what rafx.async(6).skipFrames(600)...then(v => v + 3) returns:
prt.Thenable {....
status: Status {thenable: prt.Thenable, instance: Rafx...
_argObj: {value: 10, done: true},
_breaker: {value: false}
....
The Thenable and Status constructors have nothing to do with Promises, they are completely custom.
To my surprise, this even works:
const x = Promise.resolve()
.then(() => console.log("2nd promise"))
.then(() => {
return rafx.async("hello")
.loop(function(str){
return str;
})
.until(str => window.testVar === " world!")
.then(str => str + window.testVar);
});
console.log(x instanceof Promise);
x.then((value) => console.log("3rd promise", value))
.catch((e) => console.log(e));
<script src="https://cdn.jsdelivr.net/npm/rafx"></script>
<button onclick="window.testVar = ' world!';">set testVar to ' world!'</button>
You can verify that Promise.prototype.then !== rafx.Thenable.prototype.then, the then implementation is completely separate, as seen here;
So how is it possible that Promise understands how my API works?????
(I am sure I am missing something awfully clear)
PS: I replaced all arrow functions (coz of this binding) with regular ones, it still works, but it shouldn't..
The specification for Promises is designed so that it can interoperate with other "thenables". Ie, objects that have a .then property which is a function. If you resolve a promise with a thenable (in your case by returning a thenable in a .then block), the outer promise will call that function, passing in a resolve and reject function, and will wait for the inner thenable to call resolve. Only then will the outer promise resolve.
For example:
const promise = new Promise((resolve, reject) => {
const myThenable = {
then: (resolve2, reject2) => {
console.log('running .then')
setTimeout(() => {
console.log('resolving inner');
resolve2("hello");
}, 1000)
}
}
console.log('resolving outer');
resolve(myThenable);
})
promise.then(result => {
console.log('final result', result);
})
Then code you've got for your thenable wasn't designed to work with this, but apparently it does:
prt.Thenable.prototype.then = function(f, rest){
When the promise calls your .then function, f will be the resolve function, and rest will be the reject function. Some point down the road you are calling f, which happens to have the behavior you want, and apparently it didn't cause any exceptions to have rest be a function that you weren't expecting.
I'm using bluebird and I see two ways to resolve synchronous functions into a Promise, but I don't get the differences between both ways. It looks like the stacktrace is a little bit different, so they aren't just an alias, right?
So what is the preferred way?
Way A
function someFunction(someObject) {
return new Promise(function(resolve) {
someObject.resolved = true;
resolve(someObject);
});
}
Way B
function someFunction(someObject) {
someObject.resolved = true;
return Promise.resolve(someObject);
}
Contrary to both answers in the comments - there is a difference.
While
Promise.resolve(x);
is basically the same as
new Promise(function(r){ r(x); });
there is a subtlety.
Promise returning functions should generally have the guarantee that they should not throw synchronously since they might throw asynchronously. In order to prevent unexpected results and race conditions - throws are usually converted to returned rejections.
With this in mind - when the spec was created the promise constructor is throw safe.
What if someObject is undefined?
Way A returns a rejected promise.
Way B throws synchronously.
Bluebird saw this, and Petka added Promise.method to address this issue so you can keep using return values. So the correct and easiest way to write this in Bluebird is actually neither - it is:
var someFunction = Promise.method(function someFunction(someObject){
someObject.resolved = true;
return someObject;
});
Promise.method will convert throws to rejects and returns to resolves for you. It is the most throw safe way to do this and it assimilatesthenables through return values so it'd work even if someObject is in fact a promise itself.
In general, Promise.resolve is used for casting objects and foreign promises (thenables) to promises. That's its use case.
There is another difference not mentioned by the above answers or comments:
If someObject is a Promise, new Promise(resolve) would cost two additional tick.
Compare two following code snippet:
const p = new Promise(resovle => setTimeout(resovle));
new Promise(resolve => resolve(p)).then(() => {
console.log("tick 3");
});
p.then(() => {
console.log("tick 1");
}).then(() => {
console.log("tick 2");
});
const p = new Promise(resolve => setTimeout(resolve));
Promise.resolve(p).then(() => {
console.log("tick 3");
});
p.then(() => {
console.log("tick 1");
}).then(() => {
console.log("tick 2");
});
The second snippet would print 'tick 3' firstly. Why?
If the value is a promise, Promise.resolve(value) would return value exactly. Promise.resolve(value) === value would be true. see MDN
But new Promise(resolve => resolve(value)) would return a new promise which has locked in to follow the value promise. It needs an extra one tick to make the 'locking-in'.
// something like:
addToMicroTaskQueue(() => {
p.then(() => {
/* resolve newly promise */
})
// all subsequent .then on newly promise go on from here
.then(() => {
console.log("tick 3");
});
});
The tick 1 .then call would run first.
References:
http://exploringjs.com/es6/ch_promises.html#sec_demo-promise
function createMathOperation(operator) {
console.log(operator); //(augend, addend) => augend + addend
return (value, other) => {
return operator(value, other)
}
}
const add = createMathOperation((augend, addend) => augend + addend)
add(1,2)//3
I found the above function definition from lodash. I am trying to understand it but to no avail.
Right inside createMathOperation, I try to log operator and this is the value
(augend, addend) => augend + addend
I guess value and other is 1 and 2 but how?
And how return operator(value, other) works when operator is (augend, addend) => augend + addend
Can anyone convert it to longer human readable form for a better understanding instead?
This is the essence of functional programming you can pass in a function, return a function, and call the function you received as a parameter:
function createMathOperation(operator) {
console.log(operator); // This is a the function that performs the computation
// We return a new function (using arrow syntax) that receives 2 arguments and will call the original operator we passed in to createMathOperation
// The code inside this function is not executed here, the function is not invoked.
// The caller can take the returned function and executed 0-n times as they wish.
return (value, other) => {
// when we invoke add this is the code that gets called and the arguments we pass to add end up in value and other
console.log("Getting ready to compute " + value + " operator " + other);
return operator(value, other) // since operator is a function we just invoke it as we would any other function with the two arguments we got from whoever called us.
}
}
// add will contain the wrapped function that has our extra console.log
const add = createMathOperation((augend, addend) => augend + addend)
// The 'Getting ready ...' text has not been printed yet, nobody invoked the function that was returned yet, the next line will do so.
console.log(add(1,2))
// will output:
// Getting ready to compute 1 operator 2
// 3
A note on => is just syntactic sugar over a function expression, it has extra semantics around this, but for this example, (augend, addend) => augend + addend is equivalent to function (augend, addend){ return augend + addend; }
createMathOperation returns function, which adds two numbers. Here's more readable version:
function createMathOperation(fn) {
console.log(fn);
return function(value, other){
return fn(value, other);
};
}
const add = createMathOperation(function (augend, addend) {
return augend + addend;
});
add(1,2)//3
I renamed 'operator' to 'fn' to make it less confusing (syntax highlighting colored it blue for some reason).
Your code in good old JS would look like:
var createMathOperation = function(operator) {
// operator is scope-locked within this operation wrapper
console.log('operator:', operator);
return function(value, other) {
// The wrapper returns an anonymous function that acts as a call-wrapper
// for your original function
console.log('value:', value);
console.log('other:', other);
return operator(value, other)
}
}
var add = createMathOperation(function(augend, addend) {
// This is what is being called at line 9 - return operator(value, other)
return augend + addend;
});
console.log('result 1+2:', add(1,2));
In general i don't see much use to all of this, you could just do const add = (a, v) => a + v; and have the same result.
Is there a short inbuilt way to do horizontal promise chaining?
The usual way to chain steps is:
(async()=>1)().then(_=>{
//etc..
return _+_
}).then(_=>{
//etc..
return _*_
}).then(_=>{
//etc..
return alert(_) // alert 4
})
So I use a helper to avoid repeating then:
F=(...args)=>{
let p=args[0]
for(let x=1; x<args.length;++x){
p=p.then(args[x])
}
}
F((async()=>1)(), _=>{
//etc..
return _+_
}, _=>{
//etc..
return _*_
}, _=>{
//etc..
return alert(_)
})
Another variation that overloads prototype and allows for second argument of then:
Promise.prototype.thenAll = function(...args){
let p = this;
args.forEach(_=>p=p.then(_[0], _[1]))
}
;(async()=>1)().thenAll([_=>{
//etc..
return _+_
}], [_=>{
//etc..
return _*_
}], [_=>{
//etc..
return alert(_)
}])
But is there an inbuilt way to do something akin to these?
As others have said there is no built in way to do this. The following code might be of interest though.
There is a concept known as 'lifting' whereby you transform a function into one that works on wrapped values. In this case promises. It would look something like this:
const lift = (fn) => (promise) => promise.then(fn);
There is also the idea of chaining together a list of functions, composing them with one another. Like this:
const chain = (...fns) => (value) => fns.reduce((result, fn) => fn(result), value)
Together, these tools allow you to rewrite your code as:
chain(
lift(_=>++_),
lift(_=>_*=_),
lift(_=>alert(_))
)((async()=>1)())
Which alerts 4 as expected.
I'm also a little confused by your use of ++_ and _*=_ because they imply you wish to mutate the variable. Because of how your code is structured it would be a better display of intent to use _+1 and _*_
Using Array.reduce(), you can combine the series of functions into a promise chain using the following static function:
function series (initial, ...callbacks) {
return callbacks.reduce(
(chain, callback) => chain.then(callback),
Promise.resolve(initial)
)
}
series(1, _=>_+1, _=>_*_, alert)
For convenience, you could define this as Promise.series() like this:
Object.defineProperty(Promise, 'series', {
configurable: true,
value: function series (initial, ...callbacks) { ... },
writable: true
})
Lastly, in ES2017, you could alternatively use async / await to write it like this:
async function series(initial, ...callbacks) {
let value = await initial
for (const callback of callbacks) {
value = await callback(value)
}
return value
}
series(1, _=>_+1, _=>_*_, alert)
You can use rest parameter to pass N functions to a function which return a Promise to be called in sequence with the previous Promise value set to parameter passed to the current function call until no elements remain in array using async/await and repeated scheduling of the same procedure
const a = n => new Promise(resolve =>
setTimeout(resolve, Math.floor(Math.random() * 1000), ++n));
const b = n => new Promise(resolve =>
setTimeout(resolve, Math.floor(Math.random() * 1000), n * n));
const F = async(n, ...fns) => {
try {
while (fns.length) n = await fns.shift()(n);
alert(n);
} catch (err) {
throw err
}
return n
}
F(1, a, b)
.then(n => console.log(n))
.catch(err => console.error(err));
Given that the code at Question is synchronous you can use the code at Question
((_) => (_++, _*=_, alert(_)) )(1)
Or use async/await
(async(_) => (_ = await _, _++, _ *= _, alert(_)))(Promise.resolve(1))
Obviously, given a list l and an function f that returns a promise, I could do this:
Promise.all(l.map(f));
The hard part is, I need to map each element, in order. That is, the mapping of the first element must be resolved before the the next one is even started. I want to prevent any parallelism.
I have an idea how to do this, which I will give as an answer, but I am not sure it's a good answer.
Edit: some people are under the impression that since Javascript is itself single-threaded, parallelism is not possible in Javascript.
Consider the following code:
const delay = t => new Promise(resolve => setTimeout(resolve, t));
mapAsync([3000, 2000, 1000], delay).then(n => console.log('beep: ' + n));
A naïve implementation of mapAsync() would cause "beep" to be printed out once a second for three seconds -- with the numbers in ascending order -- but a correct one would space the beeps out increasingly over six seconds, with the number in descending orders.
For a more practical example, imagine a function that invoked fetch() and was called on an array of thousands of elements.
Further Edit:
Somebody didn't believe me, so here is the Fiddle.
const mapAsync = (l, f) => new Promise((resolve, reject) => {
const results = [];
const recur = () => {
if (results.length < l.length) {
f(l[results.length]).then(v => {
results.push(v);
recur();
}).catch(reject);
} else {
resolve(results);
}
};
recur();
});
EDIT: Tholle's remark led me to this far more elegant and (I hope) anti-pattern-free solution:
const mapAsync = (l, f) => {
const recur = index =>
index < l.length
? f(l[index]).then(car => recur(index + 1).then(cdr => [car].concat(cdr)))
: Promise.resolve([]);
return recur(0);
};
FURTHER EDIT:
The appropriately named Try-catch-finally suggest an even neater implementation, using reduce. Further improvements welcome.
const mapAsync2 = (l, f) =>
l.reduce(
(promise, item) =>
promise.then(results =>
f(item).then(result => results.concat([result]))),
Promise.resolve([])
);
Rather than coding the logic yourself I'd suggest using async.js for this. Since you're dealing with promises use the promisified async-q library: https://www.npmjs.com/package/async-q (note: the documentation is much easier to read on github:https://github.com/dbushong/async-q)
What you need is mapSeries:
async.mapSeries(l,f).then(function (result) {
// result is guaranteed to be in the correct order
});
Note that the arguments passed to f is hardcoded as f(item, index, arr). If your function accept different arguments you can always wrap it up in another function to reorder the arguments:
async.mapSeries(l,function(x,idx,l){
return f(x); // must return a promise
}).then(function (result) {
// result is guaranteed to be in the correct order
});
You don't need to do this if your function accepts only one argument.
You can also just use the original callback based async.js:
async.mapSeries(l,function(x,idx,l){
function (cb) {
f(x).then(function(result){
cb(null, result); // pass result as second argument,
// first argument is error
});
}
},function (err, result) {
// result is guaranteed to be in the correct order
});
You can't use map() on its own since you should be able to handle the resolution of the previous Promise. There was a good example of using reduce() for sequencing Promises in an Google article.
reduce() allowes you to "chain" the Promise of the current item with the Promise of the previous item. To start the chain, you pass a resolved Promise as initial value to reduce().
Assume l as the input data and async() to modify the data asynchronously. It will just multiply the input data by 10.
var l = [1, 2, 3 ,4];
function async(data) {
console.log("call with ", data);
return new Promise((resolve, reject) => {
setTimeout(() => { console.log("resolve", data); resolve(data * 10); }, 1000);
});
}
This is the relevant code (its function is inline-commented)
// Reduce the inout data into a Promise chain
l.reduce(function(sequencePromise, inValue) {
/* For the first item sequencePromise will resolve with the value of the
* Promise.resolve() call passed to reduce(), for all other items it's
* the previous promise that was returned by the handler in the next line.
*/
return sequencePromise.then(function(responseValues) {
/* responseValues is an array, initially it's the empty value passed to
* reduce(), for subsequent calls it's the concat()enation result.
*
* Call async with the current inValue.
*/
return async(inValue).then((outValue) => {
/* and concat the outValue to the
* sequence array and return it. The next item will receive that new
* array as value to the resolver of sequencePromise.
*/
return responseValues.concat([outValue]);
});
});
}, Promise.resolve([]) /* Start with a resolved Promise */ ).then(function(responseValues){
console.log(responseValues);
});
The console will finally log
Array [ 10, 20, 30, 40 ]