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
Related
in js is simple exmaple
function thenable() {
return ['just function']
}
thenable.then = (resolve) => {
resolve('like a Promise')
}
(async function main() {
//behave like a function
const functionResult = thenable()
//behave like a Promise
const promiseLikeResult = await thenable
})()
Is it possible to type this behavior with typescript, when you call it's like function it returns string[](['just function']) when then or await applied it returns string ('like a Promise')?
No, you can't. When returning its value, the function has no way of knowing whether you're going to look for then on it (which is what both explicitly calling then and await both do).
There are various things you could do instead, but they seem overcomplicated and like potential maintenance concerns (vs. just having two functions each with a single clearly-defined behavior) without a really strong use case:
You could return a function that returns a function that also has a then on it, and then have the returned function do one thing while then on it does something else.
You could return an object with a then method (e.g., a thenable) that also implements toString and valueOf, doing different things in those than it does when you call then. But that relies on the calling code to trigger those methods (explicitly or implicitly).
You could return a thenable object that also has a now method or similar that the calling code can use to get the value you want returned when it's not a promise.
Of those three, the third seems to me like its probably the clearest and easiest to maintain, but I still lean toward two separate functions.
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'm making a function which takes both .toArray() and without. Is there a conditional way to add this in es6, es7 or es8?
await db
.collection(collection)
.find(params)
.toArray() //I want tis conditionally added or not
In the sense of this (this doesn't work)
multiple && .toArray()
Or is this only possible on two methods?
Is there a way to shorten a member function of an object conditionally in JavaScript?
Not really. You have a chained set of methods and you can't insert something into the chain conditionally.
My sense is that the clearest option is this:
let x = db.collection(collection).find(params);
if (someCondition) x = await x.toArray();
There are other oddball things such as putting a no-op method on the object ahead of time and then executing a method from a variable that would sometimes contain "toArray" and sometimes contain "noop".
let x = await db.collection(collection).find(params)[someCondition ? "toArray" : "noop"]();
But, I don't think I'd ever write code this way myself as I don't see it as very clear.
FYI, your particular example is a little odd because .find() returns a cursor object and .toArray() returns a promise that resolves to an array. So, you're also asking to end up with different types of data one way vs. the other. It seems like this different result type is going to have to go down a different code path anyway so there's more to the conditional branch than just this one step. That indicates one should really look at the bigger picture for the overall problem than just this step or order to come up with the best solution.
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);