Async/Await inside the Observable - javascript

How can I use async/await inside the Observable??
With this code I'm unable to trigger the unsubscribe function within observable thus interval is not cleared.
const { Observable } = require("rxjs");
const test = () => new Observable(async (subscriber) => {
await Promise.resolve();
const a = setInterval(() => {
subscriber.next(Math.random());
console.log("zz");
}, 500);
return () => {
console.log("asdsad");
clearInterval(a);
};
});
const xyz = test().subscribe(console.log);
setTimeout(() => {
xyz.unsubscribe();
}, 3000);

Async/Await inside an observable is not supported. However, it can be done with a behavior subject and an asynchronous nested function.
Create a behavior subject, convert it to an observable (.asObservable()), execute the asynchronous nested function, return the observable. Here's an example.
function getProgress() {
// Change this value with latest details
const value = new BehaviorSubject('10%');
const observable = value.asObservable();
// Create an async function
const observer = async() => {
// Perform all tasks in here
const wait1 = await new Promise(resolve => setTimeout(resolve, 3000));
value.next('66%');
const wait2 = await new Promise(resolve => setTimeout(resolve, 3000));
value.next('100%');
// Complete observable
value.complete();
}
// Call async function & return observable
observer();
return observable;
}
It's very readable and works like a charm.

First of all, subscriber passed to observable contructor cannot be async function. There is no support for that.
If you need to create observable from promise, use from:
import { from } from 'rxjs';
const observable = from(promise);
But considering your scenario.
Because there is no way to cancel native js promise, you cannot realy unsubscribe from such created observable, so:
const obs = from(new Promise(resolve => {
setTimeout(() => {
console.log('gonna resolve');
resolve('foo');
}, 1000);
}));
const sub = obs.subscribe(console.log);
setTimeout(() => sub.unsubscribe(), 500);
will print:
gonna resolve
gonna resolve
gonna resolve
(...)
so yeah: gonna resolve will be printed in the cosole all the time, but nothing more - result passed to resolve will be ignored - just not logged.
From the other hand, if you remove that unsubscribtion (setTimeout(() => sub.unsubscribe(), 500);) this time you will see:
gonna resolve
foo
gonna resolve
gonna resolve
gonna resolve
(...)
There is one way that maybe will help you - defer - but it's not strictly related with your question.
import { defer } from 'rxjs';
defer(async () => {
const a = await Promise.resolve(1);
const b = a + await Promise.resolve(2);
return a + b + await Promise.resolve(3);
}).subscribe(x => console.log(x)) // logs 7

Related

return promise for value from hook

so I'm trying to be a clever-a$$ and return a promise from a hook (so I can await the value instead of waiting for the hook to give me the value after its resolved and the hook reruns). I'm attempting something like this, and everything is working until the resolve part. The .then doesnt ever seem to run, which tells me that the resolve I set isn't firing correctly. Here's the code:
function App() {
const { valPromise } = useSomeHook();
const [state, setState] = React.useState();
React.useEffect(() => {
valPromise.then(r => {
setState(r);
});
}, []);
if (!state) return 'not resolved yet';
return 'resolved: ' + state;
}
function useSomeHook() {
const [state, setState] = React.useState();
const resolve = React.useRef();
const valPromise = React.useRef(new Promise((res) => {
resolve.current = res;
}));
React.useEffect(() => {
getValTimeout({ setState });
}, []);
React.useEffect(() => {
if (!state) return;
resolve.current(state);
}, [state]);
return { valPromise: valPromise.current, state };
}
function getValTimeout({ setState }) {
setTimeout(() => {
setState('the val');
}, 1000);
}
and a working jsfiddle: https://jsfiddle.net/8a4oxse5/
I tried something similar (re-assigning the 'resolve' part of the promise constructor) with plain functions, and it seems to work:
let resolve;
function initPromise() {
return new Promise((res) => {
resolve = res;
});
}
function actionWithTimeout() {
setTimeout(() => {
resolve('the val');
}, 2000);
}
const promise = initPromise();
actionWithTimeout();
promise.then(console.log);
jsfiddle: https://jsfiddle.net/pa1xL025/
which makes me think something is happening with the useRef or with rendering.
** update **
so it looks like the useRefs are working fine. its the final call to 'res' (or resolve) that doesn't seem to fulfill the promise (promise stays pending). not sure if a reference (the one being returned from the hook) is breaking between renders or something
If you use this code the problem is gone:
const valPromise = React.useRef();
if (!valPromise.current) {
valPromise.current = new Promise((res) => {
resolve.current = res;
})
}
Normally you shouldn't write to ref during render but this case is ok.
Explanation
When you had this initially:
const valPromise = React.useRef(new Promise((res) => {
resolve.current = res;
}));
the promise here is actually recreated on each render and only the result from first render is used.
From the docs:
const playerRef = useRef(new VideoPlayer());
Although the result of new VideoPlayer() is only used for the initial
render, you’re still calling this function on every render. This can
be wasteful if it’s creating expensive objects.
So in your case that meant the resolve.current would be updated on each render.
But the valPromise remains the initial one.
Also since the expression passed to useRef runs during rendering one shouldn't do there anything that you would not do during rendering, including side effects - which writing to resolve.current was.
As Giorgi said, the useRef runs every render, but all results after the first run are discarded, which can cause the issue I was having above. So for those interested, I made the promise implementation into a standalone hook to abstract away the complexity:
this hook has been published to NPM if interested: https://www.npmjs.com/package/usepromisevalue
export function usePromiseValue() {
const resolve = React.useRef();
const promise = React.useRef(new Promise(_resolve => {
// - useRef is actually called on every render, but the
// subsequent result is discarded
// - however, this can cause the `resolve.current` to be overwritten
// which will make the initial promise unresolvable
// - this condition takes care of ensuring we always resolve the
// first promise
if (!resolve.current) resolve.current = _resolve;
}));
return {
promise: promise.current,
resolve: resolve.current,
};
}
(*** NOTE: see the bottom of this answer for the same hook but with the ability to update the promise/resolve combo so you can resolve multiple promises instead of just 1)
keep in mind, this will only resolve the promise 1 time. if you want the promise to react to state changes and be able to fire off another async operation and resolve a new promise, you'll need a more involved implementation that will return a new promise + resolve combo.
Usage; taking the code from my question above, but tweaking:
function useSomeHook() {
const [state, setState] = React.useState();
const { promise, resolve } = usePromiseValue();
React.useEffect(() => {
getValTimeout({ setState });
}, []);
React.useEffect(() => {
if (!state) return;
resolve(state);
}, [state]);
return {
valPromise: promise,
state,
};
}
*** UPDATE ***
here's the updated promise hook that will handle giving you a new promise/resolve combo when dependecies change:
function usePromiseValue({
deps = [],
promiseUpdateTimeout = 200,
} = {}) {
const [count, setCount] = React.useState(0);
const [mountRender, setMountRender] = React.useState(true);
const resolve = React.useRef();
const promise = React.useRef(new Promise(_resolve => {
// - useRef is actually called on every render, but the
// subsequent result is discarded
// - however, this can cause the `resolve.current` to be overwritten
// which will make the initial promise unresolvable
// - this condition takes care of ensuring we always resolve the
// first promise
if (!resolve.current) resolve.current = _resolve;
}));
React.useEffect(() => {
setMountRender(false);
}, []);
React.useEffect(() => {
// - dont run this hook on mount, otherwise
// the promise will update and not be resolveable
if (mountRender) return;
setTimeout(() => {
setCount(count + 1);
}, promiseUpdateTimeout);
// - dont update the promise/resolve combo right away,
// otherwise the current promise will not resolve
// - instead, wait a short period (100-300 ms after state updates)
// to give the current promise time to resolve before updating
}, [...deps]);
React.useEffect(() => {
// - dont run this hook on mount, otherwise
// the promise will update and not be resolveable
if (mountRender) return;
promise.current = new Promise(r => resolve.current = r);
}, [count]);
return {
promise: promise.current,
resolve: resolve.current,
};
}
https://jsfiddle.net/ahmadabdul3/atgcxhod/5/

ES6: Resolving Promise containing other Promise so that parent can use .then

I have a promise which contains another API caller promise containing resolver. Now when I want to use the .then for parent promise, I am not able to do it, error says Cannot read property 'then' of undefined, below is my sample code
const getData = () => dispatch => new Promise((resolve) => {
return apiService
.getByParameter(abc)
.then((data) => {
dispatch(update({
name: data.name
}));
resolve();
})
.catch(() => {
});
});
Now whenever I try to do
this.getData().then({
<--something-->
});
It throws ne error as Cannot read property 'then' of undefined
the method getByParamter comes from a Class, as
getByParameter(...params) {
const endpoint = `${this.getEndpoint.call(this, ...params)}`;
const timeInitiated = performance.now();
return request(() => axios.get(endpoint, extraHeaders), timeInitiated,
endpoint, ACTIONS.ACTION_GET);
}
const request = (rest, timeInitiated, endpoint, action) =>
new Promise((resolve, reject) => {
rest().then(({ data }) => {
const timeResolved = performance.now();
const timeCalculated = millisToMinutesAndSeconds(timeResolved - timeInitiated);
if (endpoint !== LOGS_ENDPOINT && timeCalculated > MAX_EXECUTION_TIME) {
apiLogger.warn(`The endpoint ${endpoint} took ${timeCalculated} seconds for ${action}`);
}
resolve(data);
})
.catch((response) => {
if (!isCancel(response)) {
reject(response);
} else {
apiLogger.debug('Request cancelled');
}
});
});
Please suggest what should be the solution to achieve what I need.
Your arrow function immediately, and unconditionally returns another function, not a promise!
const getData = () => (dispatch => new Promise(...))
getData() is a function, so .then does not exist on it.
Try it yourself
console.assert(typeof getData() !== "function", "`.then` doesn't exist on a function");
Honestly, this code ought to remove the dispatch callback and let the callee use a .then handler, that's what promises are for.
const getData = async () => {
const data = await apiService.getByParameter(abc);
return update(data);
});
getData returns a function that expects a dispatch paramter. If you call that function then you get a promise.
const dispatch = useDispatch();
const myPromise = this.getData()(dispatch);
Note the empty brakets in the last line followed by the call with dispatch as argument ()(dispatch)
In other words getData creates a thunk that you can use to create the promise.
const thunkFunction = getData();
const myPromise = thunkFunction(dispatch);
myPromise.then(...)

Needs the clarity about async function

I am trying to get the value from setTimeout. but console gives no result. how to handle this async functions? can any one help me to understand this in right way?
here is my try:
async function getTheme() {
const value = 'abc'
setTimeout(() => {
return value;
}, 3000);
}
getTheme().then(time => console.log(time)); //getting no result.
That is because you're returning inside the setTimeout callback, which does not actually resolve the promise.
What you want is to instead return a promise instead:
function getTheme() {
const value = 'abc'
return new Promise(resolve => {
setTimeout(() => resolve(value), 3000);
})
}
There is no need to use async, since you are already returning a promise in the getTheme() function.
Of course, you can abstract the whole "waiting" logic into another function: then, you can keep the async if you wish:
function sleep(duration) {
return new Promise(resolve => setTimeout(resolve, duration));
}
async function getTheme() {
const value = 'abc';
await sleep(3000);
return value;
}

How to test whether a function was waited on, not only called?

Consider testing the following simplified function
const functionToBeTested = async (val) => {
await otherModule.otherFunction(val/2);
}
In my jest test I want to make sure that the otherModule.otherFunction is not only called but also waited on. In other words, I want to write a test that will fail if someone removes the await from in front of the otherFunction call.
I have so far this
test('should wait on otherFunction', () => {
await functionToBeTested(6)
expect(otherModule.otherFunction).toHaveBeenCalledWith(3);
}
But the expect(otherModule.otherFunction).toHaveBeenCalledWith(3); check does not verify that functionToBeTested has waited on otherFunction.
Here's what i came up with:
const delay = duration => new Promise(resolve => setTimeout(resolve, duration));
test('should wait on otherFunction', async () => {
let resolve;
const mockPromise = new Promise((res) => {resolve = res;});
otherModule.otherFunction.mockReturnValue(mockPromise);
const resolution = jest.fn();
functionToBeTested(6).then(resolution);
expect(otherModule.otherFunction).toHaveBeenCalledWith(3);
await delay(0);
expect(resolution).not.toHaveBeenCalled();
resolve();
await delay(0);
expect(resolution).toHaveBeenCalled();
}
So, i mock otherFunction to return a promise which starts unresolved, but i can resolve it at will during the test. Then i call the function i want to test, and give it a callback for when its complete.
I then want to assert that it did not call the callback, but since promise resolution is always asynchronous i need to add in a timeout 0 to give the promise a chance to resolve. I chose to do this with a promis-ified version of setTimeout.
And finally, i resolve the mockPromise, do a timeout 0 (again, to make sure the promise gets a chance to call its callbacks), and assert that now the resolution has been called.
If you cannot check against otherModule.otherFunction resolved value or on any side-effects, there is no need to test wether it resolves.
Otherwise, removing await in following examples will cause the tests to fail.
describe('check for side effect', () => {
let sideEffect = false;
const otherModule = {
otherFunction: x =>
new Promise(resolve => {
setTimeout(() => {
sideEffect = true;
resolve();
}, 0);
}),
};
const functionToBeTested = async val => {
await otherModule.otherFunction(val / 2);
};
test('should wait on otherFunction', async () => {
const spy = jest.spyOn(otherModule, 'otherFunction');
await expect(functionToBeTested(6)).resolves.toBeUndefined();
expect(spy).toHaveBeenCalledWith(3);
expect(sideEffect).toBe(true);
});
});
describe('check returned value', () => {
const otherModule = {
otherFunction: x =>
new Promise(resolve => {
setTimeout(() => {
resolve('hello');
}, 0);
}),
};
const functionToBeTested = async val => {
const res = await otherModule.otherFunction(val / 2);
return `*** ${res} ***`;
};
test('should wait on otherFunction', async () => {
const spy = jest.spyOn(otherModule, 'otherFunction');
const promise = functionToBeTested(6);
expect(spy).toHaveBeenCalledWith(3);
await expect(promise).resolves.toBe('*** hello ***');
});
});

Access promise instance that returns from async function

Async function automatically returns promise - I wonder if there is a way somehow to get this instance of this promise inside the function
For example if I return an actual promise like this:
const getSomePromise = () => {
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success');
}, 1000);
})
promise.someProp = 'myProp';
return promise;
}
const promise = getSomePromise();
console.log(promise.someProp);
I want to achieve the same thing with pure async function:
const sleep = ts => new Promise(resolve => setTimeout(resolve, ts));
const getSomePromise = async () => {
const p = await sleep(1000);
// some how access the instance of the promise from within the async function
// for example this['someProp'] = 'myProp';
// and return the all promise with this prop
return 'sucess';
}
const promise = getSomePromise();
console.log(promise.someProp);
Can I do that ?
Thanks
Adding a property to the promise is almost certainly a bad idea (more on that later, under However), but just to talk about how you would continue to do it:
I wonder if there is a way somehow to get this instance of this promise inside the function
No, there isn't. You could create a promise within the function and return it, but that wouldn't be the promise the function returns (it would just affect how the promise the function returns resolves).
If you want to add a property to the promise being returned, you'll have to use a non-async function. You might make the function's entire code non-async:
const sleep = ts => new Promise(resolve => setTimeout(resolve, ts));
const getSomePromise = () => {
const p = sleep(1000).then(() => 'success');
p.someProp = 'myProp';
return p;
}
const promise = getSomePromise();
console.log(promise.someProp);
...or you might use an inner async function so you can use await semantics and such:
const sleep = ts => new Promise(resolve => setTimeout(resolve, ts));
const getSomePromise = () => {
const p = (async () => {
await sleep(1000);
return 'success';
})();
p.someProp = 'myProp';
return p;
}
const promise = getSomePromise();
console.log(promise.someProp);
However: Adding a property to the promise is almost certainly a bad idea. Instead, have the promise resolve to an object with properties both for the resolution and the extra someProp:
const sleep = ts => new Promise(resolve => setTimeout(resolve, ts));
const getSomePromise = async () => {
const p = await sleep(1000);
// some how access the instance of the promise from within the async function
// for example this['someProp'] = 'myProp';
// and return the all promise with this prop
return {
result: 'success',
someProp: 'myProp'
};
}
getSomePromise()
.then(resolution => {
console.log(resolution.someProp);
});

Categories