Handle concurrent async updates to local state - javascript

I have a series of asynchronous calls that read from a local state S, perform some computation based on its current value, and return a new, update value of the local state S'
All this happens at runtime, so I have very little control over the order of these operations. This is a simplified version of what I have.
type State = {
state: number
}
let localState: State = {
state: 1000
}
const promiseTimeout = (time: number, value: number) => () => new Promise(
(resolve: (n: number) => void) => setTimeout(resolve, time, value + time)
);
const post: (n: number, currentState: State) => Promise<void> = (n, c) => promiseTimeout(n, c.state)()
.then(res => {
localState.state = res
console.log(localState)
})
post(1000, localState); // localState at call time is 1000
post(3000, localState); // localState at call time is still 1000
// when both promises resolve, the final value of localState will be 4000 instead of 5000
Playground link
This model is clearly broken, as both calls to post will read the same value of localState, while instead they should be performed sequentially.
If all calls were already determined at compile time, I could simply have something like
post(1000, localState)
.then(() => post(3000, localState)) // localState at call time is now 2000
How would I go about solving this?

One approach is to have post hook into a promise rather than working directly on the state object. That promise could be stored in the state object itself. It starts out fulfilled with the state object. post updates it like this:
const post = (n, state) => {
return state.promise = state.promise
.then(state => {
// ...do stuff here that updates (or replaces) `state`...
return state;
}));
};
Here's an example (in JavaScript, but you can add back the type annotations) using asyncAction (it's like your promiseTimeout, but without making it return a function we call immediately; not
"use strict";
let localState = {
state: 1000
};
localState.promise = Promise.resolve(localState);
// I'm not sure why this *returns* a function that we
// have to call, but...
const promiseTimeout = (time, value) => () => new Promise((resolve) => setTimeout(resolve, time, value + time));
const post = (n, state) => {
return state.promise = state.promise
.then(state => promiseTimeout(n, state.state)().then(newValue => {
state.state = newValue;
console.log(state.state);
return state;
}));
};
console.log("Running...");
post(1000, localState); // localState at call time is 1000
post(3000, localState); // localState at call time is still 1000
Since each call to post synchronously replaces the promise with a new promise, the chain is built by the calls to post.
Here's that in TypeScript (with a bit of a hack in one place, you can probably improve that); link to the playground.
type State = {
state: number,
promise: Promise<State>
};
let localState: State = (() => {
const s: Partial<State> = {
state: 1000
};
// There's probably a better way to handle this than type assertions, but...
s.promise = Promise.resolve(s as State);
return s as State;
})();
// I'm not sure why this *returns* a function that we
// have to call, but...
const promiseTimeout = (time: number, value: number) => () => new Promise(
(resolve: (n: number) => void) => setTimeout(resolve, time, value + time)
);
const post = (n: number, state: State): Promise<State> => {
return state.promise = state.promise
.then(state => promiseTimeout(n, state.state)().then(newValue => {
state.state = newValue;
console.log(state.state);
return state;
}));
};
console.log("Running...");
post(1000, localState); // localState at call time is 1000
post(3000, localState); // localState at call time is still 1000
It's worth noting that in situations like this where the state can be changed asynchronously like this, it's often worth producing a new state object when changing it rather than modifying the existing one — e.g., treat the state aspects as immutable.

This is a problem that I have personally encountered on many occasions. My solution is to create a queue class in charge of making sure that all Promise are executed in mutual exclusion. I call it PromiseQueue:
class PromiseQueue {
constructor() {
this._queue = new Array(); // Or an LinkedList for better performance
this._usingQueue = false;
}
/**
* Adds an element to the queue and runs the queue. It resolves when the promise has been executed and resolved.
*
* #param {Promise<any>} promise
*/
add(promise) {
const self = this;
return new Promise((resolve, reject) => {
const promiseData = {
promise,
resolve,
reject,
};
self._queue.push(promiseData);
self._runQueue();
});
}
async _runQueue() {
if (!this._usingQueue && this._queue.length > 0) {
this._usingQueue = true;
const nextPromiseData = this._queue.shift();
const { promise, resolve, reject } = nextPromiseData;
try {
const result = await promise();
resolve(result);
} catch (e) {
reject(e);
}
this._usingQueue = false;
this._runQueue();
}
}
}
Then you would use it like this (not tested):
const myPromiseQueue = new PromiseQueue();
// This way you are making sure that the second post
// will be executed when the first one has finished
myPromiseQueue.add(async() => await post(1000, localState));
myPromiseQueue.add(async() => await post(3000, localState));

I have no experience with TypeScript, so you'll have to do the conversion yourself.
You could considder adding a queue method to your state, which takes a callback. If the callback returns an promise it will wait for it to finish. If not the next item in the queue is immediately executed.
function createQueue() {
var promise = Promise.resolve();
return function (fn) {
promise = promise.then(() => fn(this));
return promise;
};
}
const localState = { state: 1000, queue: createQueue() };
const timeout = (...args) => new Promise(resolve => setTimeout(resolve, ...args));
const promiseTimeout = (time, value) => timeout(time, value + time);
const post = (time, state) => state.queue(() => {
return promiseTimeout(time, state.state).then(result => {
state.state = result;
console.log(state.state);
});
});
post(1000, localState).then(() => console.log("post 1000 complete"));
post(3000, localState).then(() => console.log("post 3000 complete"));

Related

Minimum time for promises

I'd like to have a minimum time applied to async logic. Ultimately this is for situations where you want to show a loading animation for at least 2-3 seconds. Maybe there's a better strategy?
Question: Why does console.log("Finish") never trigger?
Question: Why does it seem time is not being applied, e.g. "99999" occurs instantly?
Below is based on this article.
Codepen
onInit = async formData => {
console.log("-------------------")
console.log("Start")
await executeAtLeast(3000, doesSomething)
console.log("Finish")
}
onInit()
function doesSomething() {
console.log("Job being processed")
}
function promiseAllReflect(promises = []) {
const reflect = promise => promise.then(
value => ({ value, status: 'fulfilled' }),
error => ({ error, status: 'rejected' }),
)
return Promise.all(promises.map(reflect))
}
function executeAtLeast(time, func, funcArgs = []) {
return promiseAllReflect([
new Promise(resolve => setTimeout(resolve, time)),
func(...funcArgs)
])
}

Batching React updates across microtasks?

I have code that looks something like:
// File1
async function fetchData() {
const data = await fetch(...);
setState({ data });
return data;
}
// File2
useEffect(() => {
(async () => {
const data = await fetchData();
setState({ data });
})();
});
This triggers 2 React commits in 1 task. This makes my app less than 60FPS. Ideally, I'd like to batch the 2 setStates. Currently, it looks like this:
Pink represents React commits (DOM operations). The browser doesn't have a chance to repaint until the second commit is done. I can give the browser a chance to repaint by adding await new Promise(succ => setTimeout(succ, 0)); between the setStates, but it'll be better if I could batch the commits.
It's also pretty much impossible to refactor this, since the useState exists in separate files.
I tried unstable_batchedUpdates but it doesn't work with async.
You can group fetchData, when fetchData is called with the same argument the cache is checked for a promise and that promise is returned instead of creating a new one (make a new fetch).
When the promise resolves then that cache entry is removed so when component mounts again it will fetch again. To change this behaviour you can pass a different cache object to the group funciton.
//group function (will always return promise)
const createGroup = (cache) => (
fn,
getKey = (...x) => JSON.stringify(x)
) => (...args) => {
const key = getKey(args);
let result = cache.get(key);
if (result) {
return result;
}
//no cache
result = Promise.resolve(fn.apply(null, args)).then(
(r) => {
cache.resolved(key); //tell cache promise is done
return r;
},
(e) => {
cache.resolve(key); //tell cache promise is done
return Promise.reject(e);
}
);
cache.set(key, result);
return result;
};
//cache that removes cache entry after resolve
const createCache = (cache = new Map()) => {
return {
get: (key) => cache.get(key),
set: (key, value) => cache.set(key, value),
//remove cache key when resolved
resolved: (key) => cache.delete(key),
//to keep cache:
//resolved: () => 'NO_OP',
};
};
//fetch data function
const fetchData = (...args) => {
console.log('fetch data called with', args);
return new Promise((resolve) =>
setTimeout(() => resolve(args), 1000)
);
};
//grouped fetch data
const groupedFetchData = createGroup(createCache())(
fetchData
);
groupedFetchData(1, 2, 3).then((resolve) =>
console.log('resolved with:', resolve)
);
groupedFetchData(1, 2, 3).then((resolve) =>
console.log('resolved with:', resolve)
);
I think you should be able to so something along the lines of this, the aim being to cache the calls for a certain amount of time and then pass them all to unstable_batchedUpdates at once.
import { unstable_batchedUpdates } from 'reactDOM'
import raf from 'raf'
const cache = []
let rafId = null
function setBatchedState(setState, data) {
cache.push({ setState, data })
if(!rafId) {
rafId = raf(() => {
unstable_batchedUpdates(() => {
cache.forEach(({setState, data}) => setState(data))
})
rafId = null
cache = []
})
}
}
export default setBatchedState
This is using requestAnimationFrame to debounce the calls to unstable_batchedUpdates, you may prefer to use setTimeout depending on your use case.

Why doesn't .then execute after previous .then in async function?

Multiple calls to _dispatch sometimes causes the promises passed to _dispatch to be executed at the same time. Isn't .then supposed to execute after previous .then?
// Failing code
async _dispatch (promise) {
// this._mutex is a Promise
this._mutex = this._mutex.then(() => promise)
return Promise.resolve(this._mutex)
}
// Possibly working code
async _dispatch (promise) {
console.log('START_CS', promise)
while (Atomics.load(this.done, 0) === 0) {
await this.sleep(50)
}
Atomics.store(this.done, 0, 0)
console.log('IN_CS', promise)
const ret = await promise
Atomics.store(this.done, 0, 1)
console.log('END_CS', promise)
return ret
}
_dispatch is used in the following manner:
async getStatus (ports) {
const request = // ...
return this._dispatch(someAsyncFunctionReturningArray(request, ports))
}
const polling = () => {
const sleep = new Promise(resolve => setTimeout(resolve, 500))
const status = this.getStatus().then(() => {}).catch(() => {})
return Promise.all([sleep, status])
.then(polling)
}
polling()
polling() and another similar block of code is running at the same time. I noticed that someAsyncFunctionReturningArray is called concurrently.
Promises carry information about the state of a task and allow you to act on that state. They don’t, in general, represent tasks themselves. Here’s the equivalent of what you’re doing:
async function foo() {
console.log('foo() task ran');
}
function delay() {
return new Promise(resolve => {
setTimeout(resolve, 1000);
});
}
const promise = foo();
delay().then(() => promise)
This isn’t going to delay promise by a second, because promise is just an object that can say “resolved with value X”, “rejected with error Y”, or “pending”. There’s no concept of delaying promises – you delay tasks. The work is done by foo and starts when you call foo().
It’s not quite clear what the correct replacement would be in your question. I think this is what you were going for:
_dispatch (action) {
this._mutex = this._mutex.then(() => action())
return this._mutex
}
async getStatus (ports) {
const request = // ...
return this._dispatch(() => someAsyncFunctionReturningArray(request, ports))
}
but it’s possible there’s an entirely different approach that works better, and we’d need more details on what you’re trying to accomplish with this queue to recommend one.
A Promise is not a task that generates a value, it is rather a value immeadiately returned by tasks that take a while, to then somewhen pass out the result. Adding a promise to another promise through chaining them does not influence what the tasks are doing. However a callback could be called when a promise resolves, like:
async _dispatch(callback) {
this._mutex = this._mutex.then(() => callback());
return this._mutex;
}
That can then be used as:
const getStatus = (ports) => this.dispatch(() => {
const request = // ...
return someAsyncFunctionReturningArray(request, ports);
});
const sleep = new Promise(resolve => setTimeout(resolve, 500))
const polling = () => {
const status = this.getStatus().then(() => {}).catch(() => {})
return Promise.all([sleep, status])
.then(polling)
};
polling();

How to define a promise chain without using then method

I already looked for similar questions, but they are related to JQuery or any other library.
First, I wrote this:
const printIn1Sec = (value) => {
return new Promise(resolve => {
setTimeout(() => {
console.log(value);
resolve();
}, 1000)
});
};
And used it in this way:
printIn1Sec(1)
.then(() => printIn1Sec(2))
.then(() => printIn1Sec(3));
I think then is very important, because it allows us to execute something as soon as the promise is resolved.
But I was looking for something like this:
printIn1Sec(1)
.printIn1Sec(2)
.printIn1Sec(3);
I noticed I needed an object with access to this printIn1Sec method. So I defined a class:
class Printer extends Promise {
in1Sec(v) {
return this.then(() => this.getPromise(v));
}
getPromise(value) {
return new Printer(resolve => {
setTimeout(() => {
console.log(value);
resolve();
}, 1000)
})
}
}
And used it this way:
Printer.resolve().in1Sec(1).in1Sec(2).in1Sec(3);
I had to resolve the Promise from the beginning, in order to the start the chain. But it still bothers me.
Do you think, is there a way to get it working like the following?
printIn1Sec(1).printIn1Sec(2).printIn1Sec(3);
I was thinking in a new class or method, that could receive these values, store them, and finally start resolving the chain.
But it would require to call an aditional method at the end, to init with the flow.
If you really wanted to create a chainable interface as in your question, this would do it:
const printIn1Sec = (function() {
function setTimeoutPromise(timeout) {
return new Promise(resolve => setTimeout(resolve, 1000));
}
function printIn1Sec(value, promise) {
const newPromise = promise
.then(() => setTimeoutPromise(1000))
.then(() => console.log(value));
return {
printIn1Sec(value) {
return printIn1Sec(value, newPromise);
},
};
}
return value => printIn1Sec(value, Promise.resolve());
}());
printIn1Sec(1)
.printIn1Sec(2)
.printIn1Sec(3);
We just hide all the promise creation and chaining in an internal function. I split the code into smaller functions to make it a bit nicer looking.
You can try async and await
const printIn1Sec = (value) => {
return new Promise(resolve => {
setTimeout(() => {
console.log(value);
resolve();
}, 1000)
});
};
async function fun(){
await printIn1Sec(1);
await printIn1Sec(2);
await printIn1Sec(3);
}
fun();

chain promise and functions

I want to do a sort of Worker that executes a list of functions that can be promises or not. For that Worker, I use promise. Here is a exemple:
class PromiseChainer {
constructor() {
this.promise = Promise.resolve();
}
addToChain(f) {
this.promise = this.promise.then(() => Promise.resolve(f));
}
getPromise() {
return this.promise;
}
}
I want to easly add functions or promises to the chain and be sure theses function will be executed synchronously.
I tested with 2 functions:
const p = new Promise(resolve =>
setTimeout(() => resolve(console.log('promise resolved')), 500));
const f = () => console.log('value resolved')
And finally I've a:
const promiseChainer = new PromiseChainer();
promiseChainer.addToChain(f);
promiseChainer.addToChain(p);
promiseChainer.getPromise().then(() => {
console.log('finished');
});
in order to test if my functions are executed in the right order.
The problem is: I can't get this code working with Promises and Functions :
addToChain(f) {
this.promise = this.promise.then(() => Promise.resolve(f));
}
Works only with Promise (Value resolved is never displayed)
addToChain(f) {
this.promise = this.promise.then(() => f);
}
Works only with Promise (Value resolved is never displayed)
addToChain(f) {
this.promise = this.promise.then(f);
}
Works only with Functions (Promise is resolved after the message: finished).
There is a way to accomplish that I want without a if on the type of the parameter ?
Here is my playground: https://jsbin.com/vuxakedera/edit?js,console
Thanks
You're over complicating something very simple - Promises chain directly, there is no need to try to do something like what you have implemented with PromiseChainer
const p = new Promise(resolve =>
setTimeout(() => resolve(console.log('promise resolved')), 500));
const f = () => console.log('value resolved')
var chain = Promise.resolve()
.then(f)
.then(() => p)
.then(() => {
console.log('finished');
});

Categories

Resources