Waiting for redux state to meet condition - javascript

I have the following function, which is meant to allow me to wait for a particular condition to obtain in my redux state.
async function when(store, condition) {
if (condition(store.getState())) {
return;
} else {
return new Promise(resolve => {
const unsubscribe = store.subscribe(() => {
if (condition(store.getState())) {
unsubscribe();
resolve();
}
});
});
}
}
However, I'm struggling to work out if this is free of race conditions. In particular, if I have a function like...:
async function foo(store) {
await when(store, thereAreExactlyThreeTodoItems);
doSomethingThatRequiresExactlyThreeTodoItems();
}
...am I guaranteed that the condition represented by thereAreExactlyThreeTodoItems() is true when doSomethingThatRequiresExactlyThreeTodoItems() is called? Or does this need a further step to guarantee? i.e.:
async function foo(store) {
do {
await when(store, thereAreExactlyThreeTodoItems);
} while (!thereAreExactlyThreeTodoItems(store.getState());
doSomethingThatRequiresExactlyThreeTodoItems();
}
The worry I have in mind is: could an action be dispatched that invalidates the condition after resolve() is called, but before control is returned to foo()? Unfortunately, I don't have quite a good enough mental model of the javascript event loop to be sure.
Any help much appreciated.

It looks good what you are trying to do, the only thing which seems strange is that you do not cache the promise creation...
I think you have to create a map and cache the created promises so you do not create a new promise for the same functions.
In this case, I don't see a way in which you can have race conditions problems.
Something along this lines:
const cache = {}
function when(store, condition, id) {
if (condition(store.getState())) {
return;
} else if(!cache[id]) {
cache[id] = true;
return new Promise(resolve => {
const unsubscribe = store.subscribe(() => {
if (condition(store.getState())) {
unsubscribe();
resolve();
delete cache[id]
}
});
});
}
}
}

Related

Order of Async function calls

I'm making a React App for my Football team to keep track of Payments and Debts for each Member. I'm trying to search the database for Records for each Season and delete a Member if no records are found. Currently the Member gets deleted then the database searched.
This is the code block (it can also be seen with the rest of my App on GitHub here). I use Firebase to store all my data.
import database from '../firebase/firebase';
export const startRemoveMember = ( playerUuid, seasonList ) =>
{
return (dispatch, getState) =>
{
let canDelete = true;
const uid = getState().auth.uid;
// This should resolve first
seasonList.forEach( (season) =>
{
database.ref(`subs-tracker/users/${uid}/debts_and_payments/${season.seasonUuid}`)
.once('value')
.then((records) =>
{
records.forEach((childRecord) =>
{
if(childRecord.val().playerUuid === playerUuid)
{
canDelete = false;
return true; // breaks loop if record is found
};
});
});
});
// This resolves first before block above
if(canDelete)
{
alert('Deleted');
return database.ref(`subs-tracker/users/${uid}/members/${playerUuid}`)
.remove()
.then((ref) =>
{
dispatch(removeMember(playerUuid)); // Calls removeMember() function to remove the Member from the State of the App
})
}
else
{
alert('Cannot Delete. Member has records');
return false;
}
};
};
I'm probably missing something basic. I'm no expert with Databases and Async calls. Any help you would be appreciated :)
In your outer forEach callback you are creating a promise in each iteration. All those promises must resolve first.
You'll have to await all of them.
So instead of doing forEach, use map so you can collect those promises in an array. Then call Promise.all on those, so you have a promise that will resolve when all those have resolved. Then put the deletion code inside a then callback. It cannot obviously be executed synchronously.
This also means you have to think about the return value of your (dispatch, getState) => function. That function cannot give an indication of success in a synchronous way. So it should best return a promise as well.
const promises = seasonList.map( (season) =>
database.ref(`subs-tracker/users/${uid}/debts_and_payments/${season.seasonUuid}`)
.once('value')
.then((records) =>
records.forEach((childRecord) => // breaks on true
childRecord.val().playerUuid === playerUuid
);
);
);
return Promise.all(promises).then(findings =>
findings.includes(true)
).then(cannotDelete => {
if (cannotDelete) {
alert('Cannot Delete. Member has records');
return false;
} else {
alert('Deleted');
return database.ref(`subs-tracker/users/${uid}/members/${playerUuid}`)
.remove()
.then((ref) => {
dispatch(removeMember(playerUuid)); // Calls removeMember() function to remove the Member from the State of the App
return true; // To distinguish from false
});
}
});

How do I setup this JS code to do better testing?

Hi guys I'm having trouble testing the below JS using Jest. It starts with waitForWorker. if the response is 'working' then it calls waitForWorker() again. I tried Jest testing but I don't know how to test an inner function call and I've been researching and failing.
const $ = require('jquery')
const axios = require('axios')
let workerComplete = () => {
window.location.reload()
}
async function checkWorkerStatus() {
const worker_id = $(".worker-waiter").data('worker-id')
const response = await axios.get(`/v1/workers/${worker_id}`)
return response.data
}
function waitForWorker() {
if (!$('.worker-waiter').length) {
return
}
checkWorkerStatus().then(data => {
// delay next action by 1 second e.g. calling api again
return new Promise(resolve => setTimeout(() => resolve(data), 1000));
}).then(worker_response => {
const working_statuses = ['queued', 'working']
if (worker_response && working_statuses.includes(worker_response.status)) {
waitForWorker()
} else {
workerComplete()
}
})
}
export {
waitForWorker,
checkWorkerStatus,
workerComplete
}
if (process.env.NODE_ENV !== 'test') $(waitForWorker)
Some of my test is below since i can't double check with anyone. I don't know if calling await Worker.checkWorkerStatus() twice in the tests is the best way since waitForWorker should call it again if the response data.status is 'working'
import axios from 'axios'
import * as Worker from 'worker_waiter'
jest.mock('axios')
beforeAll(() => {
Object.defineProperty(window, 'location', {
value: { reload: jest.fn() }
})
});
beforeEach(() => jest.resetAllMocks() )
afterEach(() => {
jest.restoreAllMocks();
});
describe('worker is complete after 2 API calls a', () => {
const worker_id = Math.random().toString(36).slice(-5) // random string
beforeEach(() => {
axios.get
.mockResolvedValueOnce({ data: { status: 'working' } })
.mockResolvedValueOnce({ data: { status: 'complete' } })
jest.spyOn(Worker, 'waitForWorker')
jest.spyOn(Worker, 'checkWorkerStatus')
document.body.innerHTML = `<div class="worker-waiter" data-worker-id="${worker_id}"></div>`
})
it('polls the correct endpoint twice a', async() => {
const endpoint = `/v1/workers/${worker_id}`
await Worker.checkWorkerStatus().then((data) => {
expect(axios.get.mock.calls).toMatchObject([[endpoint]])
expect(data).toMatchObject({"status": "working"})
})
await Worker.checkWorkerStatus().then((data) => {
expect(axios.get.mock.calls).toMatchObject([[endpoint],[endpoint]])
expect(data).toMatchObject({"status": "complete"})
})
})
it('polls the correct endpoint twice b', async() => {
jest.mock('waitForWorker', () => {
expect(Worker.checkWorkerStatus).toBeCalled()
})
expect(Worker.waitForWorker).toHaveBeenCalledTimes(2)
await Worker.waitForWorker()
})
I think there are a couple things you can do here.
Inject status handlers
You could make the waitForWorker dependencies and side effects more explicit by injecting them into the function this lets you fully black box the system under test and assert the proper injected effects are triggered. This is known as dependency injection.
function waitForWorker(onComplete, onBusy) {
// instead of calling waitForWorker call onBusy.
// instead of calling workerComplete call onComplete.
}
Now to test, you really just need to create mock functions.
const onComplete = jest.fn();
const onBusy = jest.fn();
And assert that those are being called in the way you expect. This function is also async so you need to make sure your jest test is aware of the completion. I notice you are using async in your test, but your current function doesnt return a pending promise so the test will complete synchronously.
Return a promise
You could just return a promise and test for its competition. Right now the promise you have is not exposed outside of waitForWorker.
async function waitForWorker() {
let result = { status: 'empty' };
if (!$('.worker-waiter').length) {
return result;
}
try {
const working_statuses = ['queued', 'working'];
const data = await checkWorkerStatus();
if (data && working_statuses.includes(data.status)) {
await waitForWorker();
} else {
result = { status: 'complete' };
}
} catch (e) {
result = { status: 'error' };
}
return result;
}
The above example converts your function to async for readability and removes side effects. I returned an async result with a status, this is usefull since there are many branches that waitForWorker can complete. This will tell you that given your axios setup that the promise will complete eventually with some status. You can then use coverage reports to make sure the branches you care about were executed without worrying about testing inner implementation details.
If you do want to test inner implementation details, you may want to incorporate some of the injection principals I mentioned above.
async function waitForWorker(request) {
// ...
try {
const working_statuses = ['queued', 'working'];
const data = await request();
} catch (e) {
// ...
}
// ...
}
You can then inject any function into this, even a mock and make sure its called the way you want without having to mock up axios. In your application you simply just inject checkWorkerStatus.
const result = await waitForWorker(checkWorkerStatus);
if (result.status === 'complete') {
workerComplete();
}

Is it possible Async Await - Fluent builder approach

This my road of growing up: callbacks, promises, async/await. I keep in mind a pattern - fluent builder that looks more to interpreter. I would like to mix async/await with interpreter. Is it possible? I feel that it is not possible. But I can define why exactly.
I have:
after(async () => {
await shellContainer.uninstall()
await shellContainer.dispose()
})
I am interesting in:
after(async () => {
await shellContainer
.uninstall()
.dispose()
})
Regards.
To separate concerns, you could introduce a dedicated builder which implements the fluent interface.
With that you only need to return a promise from the final build method, making things a lot simpler.
The following is a rather crude but functional example:
class ShellContainer
{
uninstall() {
return new Promise(resolve => {
setTimeout(() => {
console.log('uninstalled');
resolve();
}, 400 + Math.random() * 600);
});
}
dispose() {
return new Promise(resolve => {
setTimeout(() => {
console.log('disposed');
resolve();
}, 400 + Math.random() * 600);
});
}
}
class ShellContainerBuilder
{
container;
plan;
constructor() {
this.reset();
}
reset() {
this.container = new ShellContainer();
this.plan = () => Promise.resolve();
}
stage(op) {
this.plan = ((prev) => () => prev().then(op))(this.plan);
}
uninstall() {
this.stage(() => this.container.uninstall());
return this;
}
dispose() {
this.stage(() => this.container.dispose());
return this;
}
build() {
return this.plan().then(() => this.container);
}
}
(async () => {
console.log('starting w/o builder:');
const shellContainer1 = new ShellContainer();
await shellContainer1.uninstall();
await shellContainer1.dispose();
console.log('w/o builder done.');
console.log('starting w/ builder:');
const shellContainer2 = await (new ShellContainerBuilder()).uninstall().dispose().build();
console.log(shellContainer2);
console.log('w/ builder done.');
})();
If you change .uninstall to return the instance (shellContainer) while assigning its Promise to a property of the instance, and retrieving that Promise in the chained call, yes, it's possible:
class ShellContainer {
uninstall() {
// Chain or construct a Promise and assign the result to a property of the instance:
this.uninstProm = new Promise((resolve, reject) => {
// do stuff
});
return this;
}
dispose() {
return this.uninstProm.then(() => {
// do stuff
});
}
}
and then,
await shellContainer
.uninstall()
.dispose()
will resolve once dispose finishes.
Note that with this approach, calling uninstall by itself may result in unexpected behavior, since .uninsall will be returning the instance synchronously, rather than the Promise. You might consider an extra argument or something to indicate whether you want to chain the uninstall call with something else, or whether you want the Promise to be returned directly, perhaps something like
class ShellContainer {
uninstall(doChain) {
// Chain or construct a Promise and assign the result to a property of the instance:
this.uninstProm = new Promise((resolve, reject) => {
// do stuff
});
return doChain ? this : this.uninstProm;
}
dispose() {
return this.uninstProm.then(() => {
// do stuff
});
}
}
and
await shellContainer
.uninstall(true)
.dispose()
or just
await shellContainer.uninstall(); // no doChain argument
But if there's only going to be one Promise, there's not much need for await in many cases - it may not make the code clearer at all. For example
after(async () => {
await shellContainer
.uninstall()
.dispose()
})
is equivalent to
after(() => shellContainer
.uninstall()
.dispose()
);
The pattern - Interpreter that implements Fluent builder is not suitable for Async/Await. Because await is applied per an operation. There is not such syntax to process async in the middle of the chain. To handle async operations you should split chaining and wrap the operations by await operator like it is at the first code snippet.

Convert async await in while loop to promises

I can't figure out how to convert async await functionality in a while loop to a promise based implementation.
repl showing the async await version
https://repl.it/repls/IdealisticPepperyCoding
var dependency = false;
function checkDependency() {
return new Promise(resolve => {
setTimeout(() => {
dependency = true;
return resolve();
}, 1000)
});
}
async function isReady() {
while(!dependency) {
console.log('not loaded');
await checkDependency();
}
console.log('loaded')
}
isReady();
If I'm understanding you correctly, you're wanting to use Promises without async functions instead of Promises with async functions, and the sample checkDependency may actually not set dependency = true in all cases, so you want to "loop."
Equivalent functionality could look something like this, where you "recursively" call the check function. It won't actually be recursive and lead to a stack overflow, though, since the call is done in an async callback:
var dependency = false;
function checkDependency() {
return new Promise(resolve => {
setTimeout(() => {
dependency = true;
return resolve();
}, 1000)
});
}
function isReady() {
if (!dependency) {
return checkDependency().then(isReady);
}
return Promise.resolve(true);
}
isReady().then(() => {
console.log('loaded')
});
You don't need the while loop.
checkDependency().then(() => { console.log('loaded');});
You don't need to return recolve() just call it.
Call the function then within the function isReady
function checkDependency() {
return new Promise(resolve => {
setTimeout(resolve, 1000);
});
}
function isReady() {
console.log('not loaded');
checkDependency().then(() => {
console.log('loaded')
});
}
isReady();

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);
}
}

Categories