I'm adding flow to a project, and I've run into an error I can't figure out. Here's some code that produces the same error:
type user = {
id: string,
};
const thing = function(): Promise<user> {
return new Promise(resolve => {
var p1 = new Promise(innerResolve => {
getData(data => {
innerResolve(data);
});
});
Promise.all([p1]).then(result => {
resolve(result[0]);
});
});
};
const getData = function(callback) {
setTimeout(() => {
callback({});
}, 1000);
};
Link to this code on Try Flow, where you can read the error.
In my application, the getData function goes out to a database to get the object. And the type of what it returns in the callback can only be this specific:
{
[key: string]: string,
}
So it could be called with an empty object, like in the example. If you replace that line with:
callback({id: 'working'})
there are no type errors. But I can't do that in my application.
Another strange thing is that I can remove this whole block:
Promise.all([p1]).then(result => {
resolve(result[0]);
});
and that also makes it pass the flow test.
And the one thing I'd think would fix this issue -- refinements -- don't resolve the issue.
if (typeof data === 'object' && typeof data.id === 'string') {
innerResolve(data);
} else {
throw new Error('bad db response');
}
I haven't been able to figure out any way to get flow to accept this code, without changing something that I can't actually change (like replacing the database code with a simple return).
Can anyone explain the error I'm seeing, and why I'm triggering it?
you are explicitly setting the return type of thing function to Promise<user>, and that's why you get the error since your callback passes the parameter {} which is NOT a user
I don't know what your specific requirement is or why you are using a timeout but if the return type can be something that is NOT user, meaning any you can make the return type: you can set the return type to Promise<user|any>
You explicitly set the return type, so you have to return that return type.
indeed, an empty object is not of type user. Instead of returning an empty object when you have no match, is to return an empty array, and make your thing function return an array.
This changes the semantics: thing returns a promise of an empty array when there is no match, or an array with one element when there is.
const thing = function(): Promise<user[]> {
... and the callback call should pass an array with zero or one elements:
callback([]);
Your sample code could also be reduced to this, but I assume you have other code that made you write it like you did:
type user = {
id: string,
};
const thing = function(): Promise<user[]> {
return new Promise(getData);
};
const getData = function(callback) {
setTimeout(() => {
callback([]);
}, 1000);
};
On Try Flow
Related
I'm trying to set a variable within a .then() command which is declared outside of it, and after the whole block finished (the .then()) I'm returning that value.
The problem is, when I return the value, the variable is undefined, but within the .then() block, the variable is loaded.
Here is the example code:
public getValueFromElement(): string {
cy.log("Obtaining the Value");
let myNumber: string; // Here I'm declaring my variable
cy.get(this.labelWithText).then(($element) => {
let originalLabelText: string = $element.text();
let splittedText: string[];
splittedText = originalLabelText.split(": ");
myNumber = splittedText[1]; // Here I'm assigning the value
cy.log("Inside the THEN" + myNumber); //This logs the number correctly
});
return myNumber; // But after I return it using the function, the value is `undefined`!
}
I'm assuming this could be related to the async / sync problem, as the return statement is being executed immediately when the function is called, and the promise created by the .then() is still running, but I don't know how to fix this.
Do you know how I can wait for the .then() to finish first before returning the value?
Thanks!!
You say "The problem is, when I return the value, the variable is undefined".
That's because the return myNumber line runs before the cy.get(this.labelWithText).then(($element) => { completes, because the command is running asynchronously.
You need to return the command itself, and also the derived myNumber is returned from inside the .then().
public getValueFromElement(): Chainable<string> { // cannot return the raw string
cy.log("Obtaining the Value");
return cy.get(this.labelWithText).then(($element) => {
...
const myNumber = splittedText[1];
cy.log("Inside the THEN " + myNumber)
return myNumber
})
}
Use it like this
getValueFromElement().then(myNumber => {
cy.log("Outside the function " + myNumber)
})
I've come to the conclusion that this works:
public async getTheNumber(): Promise<string> {
return new Promise((resolve, reject) => {
cy.log("Retrieving the number");
cy.get(this.selector).then(($element) => {
let myNumber = $element.text().split(": ")[1];
cy.log(`The Number is ${myNumber}`);
resolve(myNumber);
});
});
}
and when reading it from the test I'm doing this:
myNumberAtTestLevel = await myObject.getTheNumber();
Thing is that I've seen that I have to change my it() method to async in order for this to work.
However, I've come across this documentation of Cypress:
https://docs.cypress.io/api/utilities/promise#Syntax
I'm trying to implement the same using Cypress.Promises but I'm not able to.
Any ideas?
You can do it synchronously like this
public getValueFromElement(): string {
cy.log("Obtaining the Value");
const $element = Cypress.$(this.labelWithText)
const originalLabelText: string = $element.text()
const splitText = originalLabelText.split(": ")
const myNumber = splitText[1]
return myNumber
}
Here you sacrifice the retry options which are built into asynchronous commands.
Cypress says to use it only if you are sure the element exists already, which depends on the context of your text.
#MikhailBolotov indeed. This is how you'd handle that
cy.get("myOpenElementSelector").click() // async code
.then(() => { // must wrap sync code in then
const myNumber = getValueFromElement() // to ensure correct sequence
expect(+myNumber).to.eq(64)
})
#Mihi has the idomatic way, but it's sometimes difficult when composing page object methods.
This was an incorrect answer, but I'm keeping it here for education purposes in case someone else stumbles upon the same issue.
You can NOT use await like this:
public async getValueFromElement(): string {
cy.log("Obtaining the Value");
let myNumber: string; // Here I'm declaring my variable
let $element = await cy.get(this.labelWithText);
let originalLabelText: string = $element.text();
let splittedText: string[];
splittedText = originalLabelText.split(": ");
myNumber = splittedText[1];
return myNumber
}
But be aware that now this function being async, itself returns a promise.
Why?
Here's what the docs say:
If you're a modern JS programmer you might hear "asynchronous" and think: why can't I just use async/await instead of learning some proprietary API?
Cypress's APIs are built very differently from what you're likely used to: but these design patterns are incredibly intentional. We'll go into more detail later in this guide.
I'm writing a Discord bot that generates weekly Guild stats for text and voice channel usage. My code divides several Mongo queries up into separate methods:
function getTopActiveTextChannels() {
let topTextChannels = []
ChannelModel.find({}).sort({"messageCountThisWeek": -1}).limit(topLimit)
.exec(channels => {
channels.forEach(c => {
topTextChannels.push({"name": c.name, "messageCount": c.messageCount})
})
console.log(topTextChannels)
return topTextChannels
})
}
function getTopActiveVoiceMembers() {
let topVoiceMembers = []
UserModel.find({}).sort({"timeSpentInVoice": -1}).limit(topLimit)
.exec(users => {
users.forEach(u => {
topVoiceMembers.push({"username": u.username, "timeSpentInVoice": u.timeSpentInVoice})
})
console.log(topVoiceMembers)
return topVoiceMembers
})
}
I then have one method that calls both those and (for now) prints the values to console:
function getWeeklyGuildStats(client) {
let topActiveTextChannels = getTopActiveTextChannels()
let topVoiceMembers = getTopActiveVoiceMembers()
let promisesArray = [topActiveTextChannels, topVoiceMembers]
Promise.all(promisesArray).then(values => {console.log(values)})
}
Executing getWeeklyGuildStats(client) outputs: [ undefined, undefined ]. I am sure I'm not using promises correctly, but when I follow Mongoose's documentation, it tells me to use exec() instead of then(), but I get a channels = null error with that.
Does anything jump out to anyone? This seems like a fairly common pattern. Does anyone have a solution for how to resolve multiple Mongoose queries in a single method?
Promise.all should take an array of promises, while your functions are returning normal array, so you need to return the whole query in the helper method which getting the users and channels, then do your logic after the promise.all
your functions may look something like that
function getTopActiveTextChannels() {
return ChannelModel.find({}).sort({"messageCountThisWeek": -1}).limit(topLimit).exec();
}
function getTopActiveVoiceMembers() {
return UserModel.find({}).sort({"timeSpentInVoice": -1}).limit(topLimit).exec();
}
then the function that calls these two methods will be something like
function getWeeklyGuildStats(client) {
let topActiveTextChannels = getTopActiveTextChannels()
let topVoiceMembers = getTopActiveVoiceMembers()
let promisesArray = [topActiveTextChannels, topVoiceMembers]
Promise.all(promisesArray).then(values => {
console.log(values);
// here you could do your own logic, the for loops you did in the helper methods before
});
}
You do not have any return statements in the root level of your functions, so they are always synchronously returning undefined. I'm not familiar with the library you're using, but if for example ChannelModel.find({}).exec(callback) returns a promise with the return value of callback as your code implies, then you just need to add a return statement to your functions.
For example:
function getTopActiveTextChannels() {
let topTextChannels = []
// Return this! (Assuming it returns a promise.) Otherwise you're always returning `undefined`.
return ChannelModel.find({}).sort({"messageCountThisWeek": -1}).limit(topLimit)
.exec(channels => {
channels.forEach(c => {
topTextChannels.push({"name": c.name, "messageCount": c.messageCount})
})
console.log(topTextChannels)
return topTextChannels
})
}
I need to get the initial timestamps of all znodes in the zookeeper. I am using the getChildren method of node-zookeeper-client to do so. I am calling my getInitialTimeStamp recursively to traverse along the path. My
function looks something like this
function getInitialTimeStamp(client,path){
return new Promise((resolve,reject) => {
client.getChildren(
path,
function(error,children,stats){
//if children empty, return
if (typeof children === 'undefined' || children.length <= 0) {resolve();}
timeStamp[path]= {ctime: stats.ctime, mtime: stats.mtime};
children.map(child => {
getInitialTimeStamp(client,path+'/'+child);
});
});
});
}
it is being called like this
getInitialTimeStamp(client,path)
.then(() => {
console.log(timeStamp);
console.log("finished");
});
The problem is I can not get my .then() part to run. I know this is related to returning promise but I do not know what is being done wrong here. Consider my lack of knowledge in promises and async programming and provide me with a solution.
there are two things wrong .... if children is not empty, you never resolve ... and your children.map may as well be a forEach the way you're using it
So, firstly, you want to resolve something if children has a length, and sescondly, you only want to do so once ALL the getInitialTimeStamp of the children is finished, by use of Promise.all
function getInitialTimeStamp(client,path){
return new Promise((resolve,reject) => {
client.getChildren(
path,
function(error,children,stats){
//if children empty, return
if (typeof children === 'undefined' || children.length <= 0) {
resolve();
}
timeStamp[path]= {ctime: stats.ctime, mtime: stats.mtime};
// use promise.all to wait for all child timestamps
Promise.all(children.map(child => getInitialTimeStamp(client,path+'/'+child)))
// and then resolve this path
.then(resolve);
});
});
}
Although that can be cleaned up a bit
function getInitialTimeStamp(client, path) {
return new Promise((resolve, reject) => {
client.getChildren(path, (error, children, stats) => {
timeStamp[path]= {ctime: stats.ctime, mtime: stats.mtime};
resolve(Promise.all((children||[]).map(child => getInitialTimeStamp(client, path + '/' + child))));
});
});
}
but still no error checking is done ... i.e. test if error is truthy
I would suggest this type of implementation that promisifies at a lower level by promisifying client.getChildren(). That makes it a lot easier to write all your logic using promises and avoids common pitfalls of JaramandaX's implementation such as completely missing error handling and error propagation.
Since promises only resolve to a single value, when promisifying something that passes multiple values to its callback, you have to shoehorn each of the values into an object and resolve with that object.
Also, your implementation seems to be modifying some timeStamp global or higher scope variable which seems less than desirable. So, I've made it so you can optionally pass in an object to start with, but if you don't it will default to an empty object and, in either case, the function will return a promise that will resolve to the object filled with the desired properties, including a cnt property so you can more easily see how many are there.
getInitialTimeStamp() returns a promise that resolves to an object that contains your desired path properties.
// make promisified version that resolves to an object with properties on it
// Depending upon the situation, you might add this to the prototype rather than
// to an instance
client.getChildrenP = function(path) {
return new Promise((resolve, reject) => {
this.getChildren(path, (error, children, stats) => {
if (error) return reject(error);
resolve({children, stats});
});
});
}
// Returns a promise that resolves to a timeStamp object
// You can optionally pass in an object to be modified or that will default
// to an empty object. In either case, the returned promise resolves to
// the finished object.
function getInitialTimeStamp(client, path, resultsObj){
let obj = resultsObj || {cnt: 0};
obj.cnt = obj.cnt || 0;
return client.getChildrenP(path).then(results => {
if (typeof results.children === 'undefined' || children.length <= 0) {
// return results so far
return obj;
}
++obj.cnt;
obj[path]= {ctime: results.stats.ctime, mtime: results.stats.mtime};
return Promise.all(children.map(child => {
getInitialTimeStamp(client,path+'/'+child, obj);
})).then(results => {
return obj;
});
});
}
Usage:
getInitialTimeStamp(client, somePath).then(resultsObj => {
// process resultsObj here
}).catch(err => {
// process error here
});
I’m looking for an RxJS example how to cache a series of XHR calls (or other async operations), so the same call does not have to be repeated, while respecting immutability and with no side effects.
Here's a bare-bones, mutable example:
var dictionary = {}; // mutable
var click$ = Rx.Observable.fromEvent(document.querySelector('button'), 'click', function (evt) {
return Math.floor(Math.random() * 6) + 1; // click -> random number 1-6 (key)
})
.flatMap(getDefinition);
var clicksub = click$.subscribe(function (key) {
console.log(key);
});
function getDefinition (key) {
if ( dictionary[key] ) { // check dict. for key
console.log('from dictionary');
return Rx.Observable.return(dictionary[key]);
}
// key not found, mock up async operation, to be replaced with XHR
return Rx.Observable.fromCallback(function (key, cb) {
dictionary[key] = key; // side effect
cb(dictionary[key); // return definition
})(key);
}
JSBin Demo
Question: Is there a way to accomplish caching several similar async operations without resorting to using the dictionary variable, due to mutability and side effect?
I’ve looked at scan as a means to “collect” the XHR call results, but I don’t see how to handle an async operation within scan.
I think I’m dealing with two issues here: one is state management maintained by the event stream rather than kept in a variable, and the second is incorporating a conditional operation that may depend on an async operation, in the event stream flow.
Using the same technique than in a previous question (RxJS wait until promise resolved), you could use scan and add the http call as a part of the state you keep track of. This is untested but you should be able to easily adapt it for tests :
restCalls$ = click$
.scan(function (state, request){
var cache = state.cache;
if (cache.has(request)) {
return {cache : cache, restCallOrCachedValue$ : Rx.Observable.return(cache.get(request))}
}
else {
return {
cache : cache,
restCallOrCachedValue$ : Rx.Observable
.defer(function(){
return Rx.Observable
.fromPromise(executeRestCall(request))
.do(function(value){cache.add(request,value)})
})
}
}
}, {cache : new Cache(), restCallOrCachedValue$ : undefined})
.pluck('restCallOrCachedValue$')
.concatAll()
So basically you pass an observable which does the call down the stream or you directly return the value in the cache wrapped in an observable. In both cases, the relevant observables will be unwrapped in order by concatAll. Note how a cold observable is used to start the http call only at the time of the subscription (supposing that executeRestCall executes the call and returns immediately a promise).
Working from #user3743222's response, this code does cache the XHR calls:
// set up Dictionary object
function Dictionary () { this.dictionary = {} }
Dictionary.prototype.has = function (key) { return this.dictionary[key] }
Dictionary.prototype.add = function (key, value) { this.dictionary[key] = value }
Dictionary.prototype.get = function (key) { return this.dictionary[key] }
var definitions$ = click$
.scan(function (state, key) {
var dictionary = state.dictionary
// check for key in dict.
if ( dictionary.has(key) ) {
console.log('from dictionary')
return {
dictionary: dictionary,
def$: Rx.Observable.return(dictionary.get(key))
}
}
// key not found
else {
return {
dictionary: dictionary,
def$: Rx.Observable.fromPromise(XHRPromise(key))
.do(function (definition) { dictionary.add(key, definition) })
}
}
}, {dictionary : new Dictionary(), def$ : undefined})
.pluck('def$') // pull out definition stream
.concatAll() // flatten
Updated: In the // key not found block, executing the function XHRPromise (that returns a promise) passing in the key and the result passed to the RxJS method fromPromise. Next, chain a do method in which the definition is grabbed and added to the dictionary cache.
Follow up question: It appears we've removed the side effect issue, but is this still considered mutating when the definition and key are added to the dictionary?
Putting this here for archival purposes, the first iteration had this code, accomplishing the same caching procedure:
// ALTERNATIVE key not found
else {
var retdef = Rx.Observable.fromPromise(function () {
var prom = XHRPromise(key).then(function (dd) {
dictionary.add(key, dd) // add key/def to dict.
return dd
})
return prom
}()) // immediately invoked anon. func.
return { dictionary: dictionary, def$: retdef }
}
Here, using the function XHRPromise that returns a promise. Before returning that promise to the event stream, though, pull out a then method in which the promised definition is grabbed and added to the dictionary cache. After that the promise object is returned to the event stream through the RxJS method fromPromise.
To get access to the definition returned from the XHR call, so it can be added to the dictionary cache, an anonymous, immediately invoked function is used.
Ok I'm guessing I'm missing something really simple on this one.
Lets say I have multiple methods that repeat a lot of the same things like this:
public getDepartments(id: number): ng.IPromise<IDepartmentViewModel[]> {
this.common.loadStart();
return this.unitOfWork.teamRepository.getDepartmentsForTeam(id).then((response: IDepartmentViewModel[]) => {
this.common.loadComplete();
return response;
}).catch((error) => {
this.common.loadReset();
return error;
});
}
Tons of boilerplate for a single call to this.unitOfWork.teamRepository.getDepartmentsForTeam(id)
so I wanted to make a generic wrapper for the boilerplate such as:
private internalCall<T>(method: () => ng.IPromise<T>): ng.IPromise<T> {
this.common.loadStart();
return method().then((response: T) => {
this.common.loadComplete();
return response;
}).catch((error) => {
this.common.loadReset();
return error;
});
}
Which I could then call like:
public getDepartments(id: number): ng.IPromise<IDepartmentViewModel[]> {
return this.internalCall<IDepartmentViewModel[]>(this.unitOfWork.teamRepository.getDepartmentsForTeam(id));
But I get the following error:
Supplied parameters do not match any signature of call target:
Type '() => ng.IPromise<IDepartmentViewModel[]>' requires a call signature, but type 'ng.IPromise<IDepartmentViewModel[]>' lacks one.
What is the right way to pass my method into the other to call it with supplied parameters?
This is a common mistake: you cannot pass a method function as a regular function since it requires the instance for the class as context. The solution is to use a closure:
function foo( func: () => any ) {
}
class A {
method() : any {
}
}
var instanceOfA = new A;
// Error: you need a closure to preserve the reference to instanceOfA
foo( instanceOfA.method );
// Correct: the closure preserves the binding to instanceOfA
foo( () => instanceOfA.method() );
For a more complete example you can also see my snippet published here: http://www.snip2code.com/Snippet/28601/Typescript--passing-a-class-member-funct
I needed to wrap the call so it was wrapped in a closure like so:
public getDepartments(id: number): ng.IPromise<IDepartmentViewModel[]> {
return this.internalCall<IDepartmentViewModel[]>(
() => { return this.unitOfWork.teamRepository.getDepartmentsForTeam(id); } // Wrapping here too
);
Only for documentation - I got this error when I accidentally called the wrong (existing) function with wrong parameters. Had to look into the errorous line in the packaged file .tmp/bla/bla/bla.ts to see the error.
Try replacing your fat arrow in to normal function. This will resolve the issue.
() => ng.IPromise
to
function(){ng.IPromise .....}
In my case a simpler trick allowed me to dodge the error. The call (or trigger) of a function is due to it parentheses, so :
class MyClass {
foo: any;
firstMethod() {
this.foo = this.secondMethod;
this.foo();
}
secondMethod() {
}
}
In a more generic answer, the error "Supplied parameters do not match any signature of call target in wrapper method - Typescript" points out that you are calling a function with the wrong parameters.
example() receives two parameters per definition, but you are passing only one:
example('param1') // wrong
example('param1','param2') // OK!