I'm familiarizing myself with a codebase, and I'm seeing this everywhere:
$q.all([promise]).then(responseFunc);
This does not make sense to me -- I've read the documentation, and I don't know why the following is not used instead, since it's already one promise...
promise.then(responseFunc);
Is there something I'm missing? What's the advantage of the former over the latter?
Yes, this is a bit weird, but there is a difference: responseFunc will be called with an array of the result instead of the result itself.
This probably should better be written as either
promise.then(res => responseFunc([res]))
or
promise.then(Array.of).then(responseFunc)
Ok, here's the only advantage I can think of (based on my comment above)
function responseFunc(arr) {
arr.forEach(data => {
// do stuff with data
});
}
$q.all([promise1, promise2]).then(responseFunc);
$q.all([promise]).then(responseFunc);
Related
I have a code block like the below, it's a sync function.
I want to collect in reply info on items.
However reply is always returning as the empty array even when I have items.
When I check in the debugger, the info shows reply as a closure variable, not a local.
I'm wondering if there's something going on with hoisting here that I don't understand?
invStatus() {
let reply: string[] = []
Logger.log('player.status.items:', this.items)
if (!this.items.length) {
reply.push('nothing')
} else this.items.map(item => {
Logger.log('item', item)
reply.push[`- ${item.name}`]
})
Logger.log('player.status.reply:', reply)
return reply
}
hmm this is typescript also, I wonder if the transpiler behavior is subtly different?
I probably should use a .forEach here since I'm not capturing the return of the map or transforming things but I understand that has the same iterator behavior. The only other thing would be to go with a for x of which is more reliable, but I'd like to understand the issue here!
I think you have made a syntax error.
Observe this line:
reply.push[`- ${item.name}`]
Here, instead of parantheses, you have used square brackets.
Corrected code would be,
reply.push(`- ${item.name}`)
I know javascript but not typescript but I believe this must be the cause of the issue.
I have an async function that looks something like this:
const myMethod = async () => {
await Promise.all(
someIds.map((id) => {
someMethodThatReturnsPromise(id);
});
);
doSomethingElseAfterPromisesResolve();
};
This function contains a bug because it uses curly braces in its map function but doesn't return each promise. The Promise.all consumes the undefined return value and silently proceeds to the next statement, without waiting. The problem can be corrected by using parentheses instead of braces, or by including an explicit return statement.
My question is, how can I test this? I know I can use Promise.resolve() or Promise.reject() to simulate different states of the promise, and mock the return values of the inner method, but that doesn't really cover the problem. Outside a full blown integration test, how can I prevent the above error with a test?
Well, the issue is not that Promise.all() accepts null, it doesnt. What it accepts is arrays of the kind of [null] or [undefined] (which are 2 different things, actually)
As I mentioned in my comments, testing Promise.all() is not something I would do, it's third party code, you should be testing your own, so I think having a linter check for such bugs is a far superior solution
That being said, you are entitled to your opinions, I'll merely point out a possibility for achieving what you want. That is: monkey patching
You could overwrite the Promise.all() like so:
let originalAll = Promise.all;
Promise.all = (promises) => {
// you could check several other things
// but this covers what you wanted, right?
let isArrayWithBlanks = promises.includes(null) || promises.includes(undefined);
return isArrayWithBlanks
? Promise.reject("NO!")
: originalAll(promises);
};
Now you can easily write a test given you use this monkey patched Promise.all throughout your project. You decide where to place that code
Hope this helps
I would stub both the someMethodThatReturnsPromise and doSomethingElseAfterPromisesResolve functions, returning any value from both.
Then ensure that someIds has multiple values.
Your assertions could then be:
someMethodThatReturnsPromise is called once for each item in the someIds array
doSomethingElseAfterPromisesResolve is called
This question already has answers here:
How do I access previous promise results in a .then() chain?
(17 answers)
Closed 6 years ago.
There are many ways to make good control flows with ES2015 promises compared to alternatives like "callback hell" and similar. Lots of examples out there.
However, when the arguments for each step in the control flow depend on more than the return/resolved value of the previous step, it is harder to find a good syntax. Actual use cases are usually a bit more complex, so the simplified example syntaxes might not look that ugly, but it makes it easier to explain and discuss.
So, the question is how to make a simple (readable and easily maintainable) but flexible way of solving this kind of cases.
Example
Three operations in the control flow. Each being a function that returns a Promise.
function firstOperation(arg1) {
return new Promise(...);
}
function secondOperation(firstResponse) {
return new Promise(...);
}
function thirdOperation(firstResponse, secondResponse) {
return new Promise(...);
}
Simpler control flows
If each step just depended on the previous one, it could look simething like this:
firstOperation('foo')
.then(res1 => secondOperation(res1))
.then(res2 => thirdOperation(res2));
Or even simpler:
firstOperation('foo')
.then(secondOperation)
.then(thirdOperation);
No problem there. But in this case, thirdOperation require arguments from both of the first two operations.
The future
In ES8, I guess this could look something like:
const res1 = await firstOperation('foo');
const res2 = await secondOperation(res1);
const res3 = await thirdOperation(res1, res2);
The present
I want to use the completed standards, so I hope to find the best possible solution for these kind of cases with ES2015 syntax or possibly a promise/generator library.
Possible (but not very simple/readable) ways to solve it:
firstOperation('foo')
.then(res1 => secondOperation(res1)
.then(res2 => thirdOperation(res1, res2))
);
Making it a kind of "promise hell" that works, but in more complex cases (more than one level) would become ugly and hard to read/maintain.
Another solution:
firstOperation('foo')
.then(res1 => secondOperation(res1)
.then(res2 => ({ res1, res2 })))
.then(({ res1, res2 }) => thirdOperation(res1, res2));
Not much prettier, the only benifit compared to the previous one, is that if there where more than three operations, they could all be called on the same level, instead of indenting one level further each time. And after each operation, the result is being merged with the other ones into a "context object" keeping all the results.
Suggestions?
So, until a standardized way comes along (ES8 probably), I guess using some kind of promise/generator library would be an acceptable solution. What I mostly hope, is that I won't need to touch the functions themselves. Like still letting them (firstOperation, secondOperation and thirdOperation in the examples) get arguments the normal way, and return a promise that resolves with the value directly, instead of having to rewrite them to be able to fit different control flow use cases.
Suggestions for solving cases like this?
You could do something like this:
const res1 = firstOperation('foo');
const res2 = res1.then(secondOperation);
const res3 = Promise.all([res1, res2])
.then(args => thirdOperation(...args));
I usually declare variables before the promise chain and store the results there, where all of the calls can access them. It works, doesn't require increased indenting / deeper leveling, and it doesn't require you to change your function params.
let firstResponse;
firstOperation(userId)
.then(firstResp=>{
firstResponse = firstRestp;
return secondOperation(firstResponse);
})
.then(secondResponse=>{
return thirdOperation(firstResponse, secondResponse);
});
Edit
This answer in the linked duplicate question points out some drawbacks to this approach. For example, the variables might be used before being populated.
I have a call that returns promise. At this moment, I do this:
Something( ... )
.then(()=>{console.log("Done.");});
This would be more practical:
Something( ... )
.then(console.log, "Done.");
For example, setTimeout works like that:
setTimeout(console.log, 1000, "Done.");
Does Bluebird have any method for this? My aim is to have this practical option to reduce the already ridiculous amount of code that Promises generate.
At this moment, I do this:
Something(…).then(()=>{console.log("Done.");});
This is the correct approach. Arrow functions already shorten this a lot. Notice that you can drop the "{"…";}" parts.
This would be more practical:
Something(…).then(console.log, "Done.");
No it would not. The second parameter of then is the onRejected callback, not a string. You can't do that.
My aim is to reduce the already ridiculous amount of code that
Promises generate.
Then use async/await syntax and a transpiler. It's as simple as
await Something(…);
console.log("Done");
Does Bluebird have any method for this?
If you don't like to use a transpiler but are in an ES6 environment (like a recent Node.js), you can use generator functions to imitate async/await with Promise.coroutine.
That feature is pretty much exclusive to setTimeout. IE9 and below requires a polyfill for that anyway https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setTimeout
The following would is a workaround for your example case using console.log. Be cautious using it with any function that references this. You can use bind to set the value of this or leave it undefined. Also, it will log the resolved value of the promise after "Done" due to the value being automatically passed as the last argument to bind.
Something( ... )
.then(console.log.bind(undefined, "Done."));
#Bergi gave an excellent answer to your question. Just to add, if you use () => console.log("Done.") or some other general callback a lot, make it a separate function:
function afterSomething() {
console.log("Done.");
}
Something( ... )
.then(afterSomething);
In this article, the replier offers a correct and well structured solution to a problem.
However, he also argues that the suggested approach (i.e. making a wrapper for a callback function) isn't a valid JavaScript. That begs three questions.
Is that so?
What bad things can happen if applied?
What would be a valid JavaScript to resolve that issue?
The ... is not valid syntax. There are two solutions:
First, you could manually list out a lot of parameters:
callback: function (jq1, jq2, jq3, jq4, jq5, jq6) {
return pageselectCallback(your1, your2, jq1, jq2, jq3, jq4, jq5, jq6);
}
This of course won't work if there are more than six parameters. To fix this, you can use the .apply method, which takes an array of parameters:
callback: function () {
return pageselectCallback.apply(null,
Array.prototype.concat.call([your1, your2], arguments));
}
The example code in the answer is not valid. (, ...,)
But the solution is valid.