Below is the code which I am using.
I am working with Cypress + Cucumber + Typescript.
Scenario: I need to get a list of unique values using a for loop. Then I am passing this value to an API to verify some condition and if the condition is met I want to exit the loop.
To exit the loop I somewhere read a solution that if I use "return false" as first-line in if condition then loop will exit which seems to work fine.
The issue here is, when I try to set a flag from inside the for-if loop to the instance variable then the value read by if condition (for exiting the loop) is not picking the updated value of instance variable. And the loop continues to run.
Below is the code snippet:
class test {
static isVinavailable: boolean = false;
static setEligibleVehicleVinTest() {
cy.xpath(eligibleForSaleVehicleVin).then((esv) => {
const listingCount = Cypress.$(esv).length;
for (let i = 0; i < listingCount; i++) {
let text123 = esv.eq(i).text();
genericAction.getAuthenticationKey();
cy.fixture("authResp.json")
.then((authResp) => {
cy.request({
method: "GET",
url: vehicleCheckEligibility + text123,
headers: {
Authorization: authResp.access_token,
},
});
})
.then((response: any) => {
cy.wait(5000);
let responseDataelig = response.body;
if (
(responseDataelig.val1 =
"Y" &&
responseDataelig.val2 === "N" &&
responseDataelig.val3 === "N")
) {
this.isVinavailable = true;
}
});
if (this.isVinavailable) {
return false;
}
}
});
}
}
class test {
static isVinavailable = false;
static setEligibleVehicleVinTest(): Cypress.Chainable<boolean> {
return cy.xpath(eligibleForSaleVehicleVin).each(($el) => {
let text123 = $el.text();
cy.fixture('authResp.json')
.then((authResp) => {
return cy.request({
// your code block
});
})
.then((response: any) => {
// your code block
if (condition) {
this.isVinavailable = true;
return false;
};
});
}).then(() => {
return this.isVinavailable;
});
}
}
I have a auth.service and data.service. auth.service getting data from data.service but it checks before data arrives. So it returns undefined.
auth.service getting data like this;
get isLoggedIn(): boolean {
const user = JSON.parse(localStorage.getItem('user'));
const emailVerify = this.dataservice.userStatService(user.uid);
console.warn(emailVerify)
return (user !== null && emailVerify !== false && emailVerify !== undefined ) ? true : false;
}
data.service check user status function like this;
userStatService(uid: any): any{
console.error(uid)
this.get(uid)
.subscribe(
data => {
console.warn('status set', data.status)
this.statData = data.status;
},
error => {
console.log(error);
});
return this.statData;
}
and this code works like this now;
See console logs
I'm waiting for your code examples, thank you.
Update:
auth.guard code;
canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
if (this.authService.isLoggedIn() !== true) {
this.router.navigate(['/auth/login'], { queryParams: { returnUrl: 'dashboard' } })
.then(() => {
this.authService.SignOut();
});
}else{
return true;
}
}
observables execute asynchronously, you need to return the observable and subscribe in the consumer to use it correctly:
// return observable
userStatService(uid: any): any{
console.error(uid)
return this.get(uid)
}
isLoggedIn() {
const user = JSON.parse(localStorage.getItem('user'));
this.dataservice.userStatService(user.uid).subscribe(emailVerify => {
console.warn(emailVerify)
})
// really can't do this while working with async execution. doesn't work.
//return (user !== null && emailVerify !== false && emailVerify !== undefined ) ? true : false;
}
if this is for a guard, use the map operator and return the whole observable, angular expects either a boolean or an observable:
isLoggedIn(): Observable<boolean> {
const user = JSON.parse(localStorage.getItem('user'));
if (!user)
return of(false); // null guard and slight performance improvement
return this.dataservice.userStatService(user.uid).pipe(map(emailVerify => {
console.warn(emailVerify)
return (emailVerify !== false && emailVerify !== undefined ) ? true : false;
}))
}
and in your guard you need to again, RETURN THE OBSERVABLE:
canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
return this.authService.isLoggedIn().pipe(
map(isLoggedIn => {
if (!isLoggedIn) {
this.router.navigate(['/auth/login'], { queryParams: { returnUrl: 'dashboard' } }).then(() => {
this.authService.SignOut();
});
}
return isLoggedIn;
})
)
}
angular guards will handle the subscribing, but you must return the observable to the guard.
I have a working subscription that uses withFilter:
User_Presence_Subscription: {
subscribe: withFilter(
() => pubsub.asyncIterator(USER_PRESENCE_UPDATED_CHANNEL),
(payload, args, context) => {
if (typeof (payload) === 'undefined') {
return false;
}
const localUserId = (typeof(context) == 'undefined' || typeof(context.userId) == 'undefined') ? null : context.userId;
const ids_to_watch = args.ids_to_watch;
const usersWithUpdatedPresence = payload.User_Presence_Subscription;
let result = false;
console.log("User_Presence_Subscription - args == ", args, result);
return result;
}
)
}
I'd like to modify the payload before sending it to the client. I tried adding a resolve function as shown in the docs:
User_Presence_Subscription: {
resolve: (payload, args, context) => {
debugger; <== NEVER ACTIVATES
return {
User_Presence_Subscription: payload,
};
},
subscribe: withFilter(
() => pubsub.asyncIterator(USER_PRESENCE_UPDATED_CHANNEL),
(payload, args, context) => {
if (typeof (payload) === 'undefined') {
return false;
}
const localUserId = (typeof(context) == 'undefined' || typeof(context.userId) == 'undefined') ? null : context.userId;
const ids_to_watch = args.ids_to_watch;
const usersWithUpdatedPresence = payload.User_Presence_Subscription;
let result = false;
console.log("User_Presence_Subscription - args == ", args, result);
return result;
}
)
}
...but the debugger line in the resolve function never gets hit.
What's the correct syntax to use here?
Solved. The only reason the resolver wasn't being hit was that in my test code I was returning false from the withFilter function. When it returns true the resolver is hit as expected.
I'm trying to use rxjs in conjunction with babeljs to create an async generator function that yields when next is called, throws when error is called, and finishes when complete is called. The problem I have with this is that I can't yield from a callback.
I can await a Promise to handle the return/throw requirement.
async function *getData( observable ) {
await new Promise( ( resolve, reject ) => {
observable.subscribe( {
next( data ) {
yield data; // can't yield here
},
error( err ) {
reject( err );
},
complete() {
resolve();
}
} );
} );
}
( async function example() {
for await( const data of getData( foo ) ) {
console.log( 'data received' );
}
console.log( 'done' );
}() );
Is this possible?
I asked the rubber duck, then I wrote the following code which does what I wanted:
function defer() {
const properties = {},
promise = new Promise( ( resolve, reject ) => {
Object.assign( properties, { resolve, reject } );
} );
return Object.assign( promise, properties );
}
async function *getData( observable ) {
let nextData = defer();
const sub = observable.subscribe( {
next( data ) {
const n = nextData;
nextData = defer();
n.resolve( data );
},
error( err ) {
nextData.reject( err );
},
complete() {
const n = nextData;
nextData = null;
n.resolve();
}
} );
try {
for(;;) {
const value = await nextData;
if( !nextData ) break;
yield value;
}
} finally {
sub.unsubscribe();
}
}
I think a problem with this solution is that the observable could generate several values in one batch (without deferring). This is my proposal:
const defer = () => new Promise (resolve =>
setTimeout (resolve, 0));
async function* getData (observable)
{
let values = [];
let error = null;
let done = false;
observable.subscribe (
data => values.push (data),
err => error = err,
() => done = true);
for (;;)
{
if (values.length)
{
for (const value of values)
yield value;
values = [];
}
if (error)
throw error;
if (done)
return;
await defer ();
}
}
When using Observables for certain tasks that involve a lot of chaining and a lot of asynchronous operations, such as listing all the items in a folder and checking all of the folders in it for a specific file, I often end up either needing to build the complex chain for each task (return Observable.of(folder)...) or having some kind of special value that gets forwarded to the end to signal the end of a batch (every operator starts with if(res === false) return Observable.of(false)).
Sort of like that stick that you put between your groceries and those of the person in front of you at the checkout.
It seems like there should be a better way that doesn't involve forwarding a stop value through all kinds of callbacks and operators.
So what is a good way to call a function that takes a folder path string and returns a list of all the files and folders in it. It also specifies whether the files are HTML files or not, and whether or not the folders contain a file called tiddlywiki.json.
The only requirement is that it can't return anything like Observable.of(...).... It should probably have a subject at the top of the chain, but that is not a requirement.
function listFolders(folder) {
return [
{ type: 'folder', name: 'folder1' },
{ type: 'datafolder', name: 'folder2' }, //contains "tiddlywiki.json" file
{ type: 'folder', name: 'folder3' },
{ type: 'htmlfile', name: 'test.html' },
{ type: 'other', name: 'mytest.txt' }
]
}
Here is one that does not follow the rules I layed out (see below for one that does), but it took about ten minutes, using the first one as a guide.
export function statFolder(subscriber, input: Observable<any>) {
return input.mergeMap(([folder, tag]) => {
return obs_readdir({ folder, tag })(folder);
}).mergeMap(([err, files, { folder, tag }]) => {
if (err) { return Observable.of({ error: err }) as any; }
else return Observable.from(files).mergeMap(file => {
return obs_stat([file,folder])(path.join(folder, file as string));
}).map(statFolderEntryCB).mergeMap<any, any>((res) => {
let [entry, [name, folder]] = res as [any, [string, string, number, any]];
if (entry.type === 'folder')
return obs_readdir([entry])(path.join(entry.folder, entry.name));
else return Observable.of([true, entry]);
}, 20).map((res) => {
if (res[0] === true) return (res);
let [err, files, [entry]] = res as [any, string[], [FolderEntry, number, any]];
if (err) {
entry.type = "error";
} else if (files.indexOf('tiddlywiki.json') > -1)
entry.type = 'datafolder';
return ([true, entry]);
}).reduce((n, [dud, entry]) => {
n.push(entry);
return n;
}, []).map(entries => {
return { entries, folder, tag };
}) as Observable<{ entries: any, folder: any, tag: any }>;
}).subscribe(subscriber);
}
Original: This took a few hours to write...and it works...but...it uses concatMap, so it can only take one request at a time. It uses a custom operator that I wrote for the purpose.
export function statFileBatch(subscriber, input: Observable<any>) {
const signal = new Subject<number>();
var count = 0;
//use set timeout to fire after the buffer recieves this item
const sendSignal = (item) => setTimeout(() => { count = 0; signal.next(item); });
return input.concatMap(([folder, tag]) => {
return obs_readdir({ folder, tag })(folder);
}).lift({
call: (subs: Subscriber<any>, source: Observable<any>) => {
const signalFunction = (count) => signal.mapTo(1), forwardWhenEmpty = true;
const waiting = [];
const _output = new Subject();
var _count = new Subject<number>()
const countFactory = Observable.defer(() => {
return Observable.create(subscriber => {
_count.subscribe(subscriber);
})
});
var isEmpty = true;
const sourceSubs = source.subscribe(item => {
if (isEmpty && forwardWhenEmpty) {
_output.next(item);
} else {
waiting.push(item)
}
isEmpty = false;
})
const pulse = new Subject<any>();
const signalSubs = pulse.switchMap(() => {
return signalFunction(countFactory)
}).subscribe(count => {
//act on the closing observable value
var i = 0;
while (waiting.length > 0 && i++ < count)
_output.next(waiting.shift());
//if nothing was output, then we are empty
//if something was output then we are not
//this is meant to be used with bufferWhen
if (i === 0) isEmpty = true;
_count.next(i);
_count.complete();
_count = new Subject<number>();
pulse.next();
})
pulse.next(); //prime the pump
const outputSubs = Observable.create((subscriber) => {
return _output.subscribe(subscriber);
}).subscribe(subs) as Subscription;
return function () {
outputSubs.unsubscribe();
signalSubs.unsubscribe();
sourceSubs.unsubscribe();
}
}
}).mergeMap(([err, files, { folder, tag }]) => {
if (err) { sendSignal(err); return Observable.empty(); }
return Observable.from(files.map(a => [a, folder, files.length, tag])) as any;
}).mergeMap((res: any) => {
let [file, folder, fileCount, tag] = res as [string, string, number, any];
return obs_stat([file, folder, fileCount, tag])(path.join(folder, file))
}, 20).map(statFolderEntryCB).mergeMap<any, any>((res) => {
let [entry, [name, folder, fileCount, tag]] = res as [any, [string, string, number, any]];
if (entry.type === 'folder')
return obs_readdir([entry, fileCount, tag])(path.join(entry.folder, entry.name));
else return Observable.of([true, entry, fileCount, tag]);
}, 20).map((res) => {
//if (res === false) return (false);
if (res[0] === true) return (res);
let [err, files, [entry, fileCount, tag]] = res as [any, string[], [FolderEntry, number, any]];
if (err) {
entry.type = "error";
} else if (files.indexOf('tiddlywiki.json') > -1)
entry.type = 'datafolder';
return ([true, entry, fileCount, tag]);
}).map(([dud, entry, fileCount, tag]) => {
count++;
if (count === fileCount) {
sendSignal([count, tag]);
}
return entry;
}).bufferWhen(() => signal).withLatestFrom(signal).map(([files, [sigResult, tag]]: any) => {
return [
typeof sigResult !== 'number' ? sigResult : null, //error object
files, //file list
typeof sigResult === 'number' ? sigResult : null, //file count
tag //tag
];
}).subscribe(subscriber);
}