Recursive observable method with delay - javascript

I am trying to create recursive server connection method with delay in Angular 8.
I tried this:
public connectToServerSafely(): Observable<boolean> {
if (this.isConnecting) {
return this.connectToServerSafely().pipe(delay(5000));
} else if (this.isConnected) {
return of(true);
} else {
return this.connectToServer();
}
}
Where connectToServer method returns Observable< boolean> depends on connection succeed or failed.
The problem is this delay method, I do not know why but I am facing with almost 2000 calls of connectToServerSafely() method until connection established. Connection is established after 1 second.
Why delay method does not really postopone recursive call of connectToServerSafely method (something as setTimeout method does)?

this.connectToServerSafely().pipe(delay(5000)) will not stop the function from calling itself. The delay operator will just delay the emitted values.
Here's my approach:
let isConnecting = true;
let isConnected = false;
timer(2000)
.subscribe(() => (isConnecting = false, isConnected = true));
function connSafely ()/* : Observable<any> */ {
console.warn('calling `connSafely`');
if (isConnecting) {
return timer(500).pipe(concatMap(() => connSafely()))
}
if (isConnected) {
return of(true);
}
return connectToServer();
}
function connectToServer () {
isConnecting = true;
return of('connecting');
}
connSafely().subscribe();
console.warn("calling 'connSafely'") should be called 5 times(1 for initial function call one 4 because 2000 / 500 = 4).
Note: It is important that you use one of the higher-order mapping operators(switchMap, concatMap, mergeMap/flatMap, exhaustMap) in order to make sure that all the subsequent function calls are automatically subscribed to/unsubscribed from.
Try to use tap(() => connSafely()) and you should only see message twice in the console.
StackBlitz. (scroll down until you find your example)

so looks like you set this.isConnecting and this.isConnected some where at the subscription to stream. If so code run async and you exit condition does not work because you design it on sync way
public connectToServerSafely(): Observable<boolean> {
if (this.isConnecting) {
// here function will be called until this.isConnecting change to false
return this.connectToServerSafely().pipe(delay(5000));
} else if (this.isConnected) {
return of(true);
} else {
return this.connectToServer();
}
}

This solution works for me:
public connectToServerSafely(): Observable<boolean> {
if (this.isConnecting) {
return new Observable((observer) => {
setTimeout(() => {
this.connectToServerSafely().subscribe(result => {
observer.next(result);
observer.complete();
});
}, 500);
});
} else if (this.isConnected) {
return of(true);
} else {
return this.connectToServer();
}
}

Related

Return two different things inside same return in JavaScript/AngularJS

There is a service which gets the data and has a then-catch structure:
getData(siteId) {
const accessToken = JSON.parse(sessionStorage.getItem('ls.authorizationData')).token;
const config = { ...
};
this.canceler.resolve();
this.canceler = this.$q.defer();
config.timeout = this.canceler.promise;
const siteIds = this.MyService.getSitesIds();
return this.$http.get(`${TEST_API_URL}value?siteIds=${siteId || siteIds}`, config)
.then(result => {
return {
data: result.data,
};
}).catch((e) => {
if (e.status === -1 && e.xhrStatus === "abort") {
return Promise.resolve({canceled: true});
}
debugger;
this.hasData = false;
return Promise.reject('Cannot access data ');
});
}
I'm calling this function from the controller, initially in $onInit() like this:
$onInit() {
getData.call(null, this);
}
and as a function:
function getData(MyCtrl) {
MyCtrl.loading = true;
MyCtrl.MyService.getData(MyCtrl.siteId).then(result => {
if (result.canceled) {
return;
}
...
}
This works fine.
The problem appears when I want to send a variable from service to controller if data is not there (if the catch() happens).
I tried to change the return by wrapping the Promise inside an object like
return {promise: Promise.reject('Cannot access data ')};
and add the variable inside this object:
return {promise: Promise.reject('Cannot access data ')
hasData: false};
but it seems that it's not the right way. I need to know in the controller if the data was got or not.
Do you have any suggestions how to solve this?
Normally when you want to return more data from a Promise rejection, you do it the same way you would return from a normal exception, you extend from the Error object.
Here is an example..
class MyError extends Error {
constructor (message, extra) {
super(message);
this.extra = extra;
}
}
//standard promise rejection way.
function badPromise() {
return Promise.reject(
new MyError("Bad Error", "Extra Stuff")
);
}
//works if throw error inside an async function
async function badPromise2() {
throw new MyError("Bad Error2", "Extra Stuff2");
}
async function test() {
try {
if (Math.random() > 0.5) await badPromise();
else await badPromise2();
} catch(e) {
if (e instanceof MyError) {
console.error(`${e.message}, extra = ${e.extra}`);
} else {
console.error(`${e.toString()}`);
}
}
}
test();
ps. This is using new ESNext features, but the same applies if doing ES5..

Waiting for an element to be in the DOM using promises or generator functions

What I am trying to achieve is to be able to use a promise when checking if an element is currently in the DOM. My idea is to get away from setTimeOut but really using window.requestAnimationFrame is exactly the same as setTimeOut only a little more performant.
const doCheck = function(selector){
if (document.querySelector(selector) === null) {
window.requestAnimationFrame(function () {
doCheck(selector);
});
} else {
return true;
}
};
export default function checkElement(selector) {
return new Promise(res => {
doCheck(selector);
res(true);
})
}
But of course this cannot work, the major problem is the fact you call another function when the selector is not there, how can I encapsulate this with a promise? Seems to be that you cannot do something like this.
Your doCheck function is asynchronous but neither takes a callback nor returns a promise. The promise inside checkElement is getting immediately resolved.
#Amit's answer shows how to pass around a callback (and use it instead of return), here's how to do it properly with promises:
function rafAsync() {
return new Promise(resolve => {
requestAnimationFrame(resolve);
});
}
export default function checkElement(selector) {
if (document.querySelector(selector) === null) {
return rafAsync().then(() => checkElement(selector));
} else {
return Promise.resolve(true);
}
}
Since you were talking about generator functions, I assume you want to use async/await. You can do that recursively:
export default async function checkElement(selector) {
if (document.querySelector(selector) === null) {
await rafAsync();
return checkElement(selector);
} else {
return true;
}
}
Or iteratively:
export default async function checkElement(selector) {
while (document.querySelector(selector) === null) {
await rafAsync()
}
return true;
}
you can send your resolve function and call it when you finish
const doCheck = function(selector,resolve){
if (document.querySelector(selector) === null) {
window.requestAnimationFrame(function () {
doCheck(selector,resolve);
});
} else {
resolve(true);
}
};
export default function checkElement(selector) {
return new Promise(res => {
doCheck(selector,res);
})
}
doCheck(selector);
res(true);
So on line one of the above you start polling for the element.
Then you immediately resolve the promise.
That won't work.
You need to resolve the promise only when you've found the element!
i.e. on this line:
return true;

How to resolve a promise multiple times?

It might sound weird, but I'm looking for a way to resolve a promise multiple times. Are there any approaches to make this possible?
Think of the following example:
getPromise() {
const event = new Event('myEvent');
setTimeout(() => {
window.dispatchEvent(event);
}, 5000);
setTimeout(() => {
window.dispatchEvent(event);
}, 7000);
return new Promise((resolve) => {
window.addEventListener('myEvent', () => {
resolve('some value'));
});
resolve('some value'));
});
};
And then .then():
getPromise().then(data => {console.log(data)})
Should give the following result:
some value // initial
some value // after 5000ms
some value // after 7000ms
So I know there are libraries to stream data, but I'm really looking for a native non-callbak approach to achieve this.
How to resolve a promise multiple times?
You can't. Promises can only be resolved once. Once they have been resolved, they never ever change their state again. They are essentially one-way state machines with three possible states pending, fulfilled and rejected. Once they've gone from pending to fulfilled or from pending to rejected, they cannot be changed.
So, you pretty much cannot and should not be using promises for something that you want to occur multiple times. Event listeners or observers are a much better match than promises for something like that. Your promise will only ever notify you about the first event it receives.
I don't know why you're trying to avoid callbacks in this case. Promises use callbacks too in their .then() handlers. You will need a callback somewhere to make your solution work. Can you explain why you don't just use window.addEventListener('myEvent', someCallback) directly since that will do what you want?
You could return a promise-like interface (that does not follow Promise standards) that does call its notification callbacks more than once. To avoid confusion with promises, I would not use .then() as the method name:
function getNotifier() {
const event = new Event('myEvent');
setTimeout(() => {
window.dispatchEvent(event);
}, 500);
setTimeout(() => {
window.dispatchEvent(event);
}, 700);
let callbackList = [];
const notifier = {
notify: function(fn) {
callbackList.push(fn);
}
};
window.addEventListener('myEvent', (data) => {
// call all registered callbacks
for (let cb of callbackList) {
cb(data);
}
});
return notifier;
};
// Usage:
getNotifier().notify(data => {console.log(data.type)})
I have a solution in Typescript.
export class PromiseParty {
private promise: Promise<string>;
private resolver: (value?: string | PromiseLike<string>) => void;
public getPromise(): Promise<string> {
if (!this.promise) {
this.promise = new Promise((newResolver) => { this.resolver = newResolver; });
}
return this.promise;
}
public setPromise(value: string) {
if(this.resolver) {
this.resolver(value);
this.promise = null;
this.resolver = null;
}
}
}
export class UseThePromise {
public constructor(
private promiseParty: PromiseParty
){
this.init();
}
private async init(){
const subscribe = () => {
const result = await this.promiseParty.getPromise();
console.log(result);
subscribe(); //resubscribe!!
}
subscribe(); //To start the subscribe the first time
}
}
export class FeedThePromise {
public constructor(
private promiseParty: PromiseParty
){
setTimeout(() => {
this.promiseParty.setPromise("Hello");
}, 1000);
setTimeout(() => {
this.promiseParty.setPromise("Hello again!");
}, 2000);
setTimeout(() => {
this.promiseParty.setPromise("Hello again and again!");
}, 3000);
}
}

make search in Observable sync

I want to check, if given clientName is present in Observable Collection, but in cause of async run, i don't get "false" return at all. How to transform my function to sync - i would like not to use callbacks - just return true/false
checkIfClientNameIsUnique(clientName: string): boolean {
var isUnique = true;
this.getAll()
.subscribe(clients => {
clients.forEach(client => {
if (clientName == client.name) {
isUnique = false
}
})
});
return isUnique
}
I see three options:
make checkIfClientNameIsUnique to return Promise then you
can use it like checkIfClientNameIsUnique(name).then(isUnique =>
{...})
Load all clients to array on initial state. I suppose you have ClientsService there you can put clients array, then your
checkIfClientNameIsUnique method can be sync and use already loaded
clients array.
3.If you emit to ES6 you can use async await keywords and it will look like this.
checkIfClientNameIsUnique(clientName: string): Promise<boolean> {
return new Promise((resolve, reject) => {
this.getAll()
.subscribe(clients => {
for (let client of clients) {
if (clientName == client.name) {
resolve(false);
break;
}
}
resolve(true);
});
});
}
// ...
async main() {
let isUnique = await checkIfClientNameIsUnique(name);
}

Angular2 Observable with interval

I have a function that needs to be called about every 500ms. The way I am looking at doing it with angular2 is using intervals and observables. I have tried this function to create the observable:
counter() {
return Observable.create(observer => {
setInterval(() => {
return this.media.getCurrentPosition();
}, 500)
})
}
With this code for the subscriber:
test() {
this.playerService.initUrl(xxxx) // This works
this.playerService.counter().subscribe(data => {
res => {
console.log(data);
}
})
}
I am very new to observables and angular2 so I might be taking the wrong approach completely. Any help is appreciated.
The Observable class has a static interval method taking milliseconds (like the setInterval method) as a parameter:
counter() {
return Observable
.interval(500)
.flatMap(() => {
return this.media.getCurrentPosition();
});
}
And in your component or wherever:
test() {
this.playerService.initUrl(xxxx) // This works
this.playerService.counter().subscribe(
data => {
console.log(data);
}
);
}

Categories