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)
])
}
Related
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.
I get problems with async/await functions and changing state in React.
This is my async function, which is triggered by clicking on the button:
async startNewEntry() {
this.addIssue();
let issue_id;
console.log(this.state.timeEntry, "started_timeEntry")
if (this.state.timeEntry?.issue?.id) {
issue_id = this.state.timeEntry?.issue?.id;
} else {
issue_id = (await this.issueService.list()).data[0]?.id;
}
const { data } = await this.timeEntryService.create({
comments: this.state.timeEntry.comments,
issue_id,
spent_on: moment(new Date()).format("YYYY-MM-DD"),
hours: 0.01,
activity_id: this.localStorageService.get("defaultActivityId")
});
In this function I use this.addIssue, which use this.createIssue, which changing my class component state:
addIssue() {
this.projectService.list([]).then(response => {
response.data = response.data.filter((x: any) => x.status === 1);
this.setState(
{
activeProjects: response.data
},
() => {
this.createIssue();
}
);
});
}
createIssue() {
this.issueAddService
.create({
project_id: this.state.activeProjects[0].id,
tracker_id: trakerId,
priority_id: priorityId,
subject: this.state.timeEntry.comments,
description: this.state.timeEntry.comments
})
.then(response => {
let timeEntry = this.state.timeEntry;
timeEntry.issue = response.data;
this.setState({
timeEntry
});
})
.catch(error => {
console.log("error", error);
});
}
As you can see, in my async function I new to have my new State, but actually async function works before my this.addIssue function. I know that question might be little freaky, but Thanks in forward!!
I am not a React expert, but I don't fully understand why there are lot of setState invocations spread around the place.
If you leave the setState to the end of the function, then you might not need to worry about correctly sequencing asynchronous calls to it (although the other answer does show how this can be achieved).
Perhaps invoking it once might make the code clearer. I welcome corrections...
async startNewEntry() {
const activeProjects = await fetchActiveProjects()
const issue = await this.createIssue()
const timeEntry = await createTimeEntry({ issue, comments: this.state.timeEntry.comments })
this.setState({ activeProjects, issue, timeEntry })
}
async fetchActiveProjects() {
const { data } = await this.projectService.list([])
return data.filter(({ status }) => status === 1)
}
async createIssue() {
const { data } = await this.issueAddService.create({
project_id: this.state.activeProjects[0].id,
tracker_id: trakerId,
priority_id: priorityId,
subject: this.state.timeEntry.comments,
description: this.state.timeEntry.comments
})
return data
}
async createTimeEntry({issue, comments}) {
const { data } = await this.timeEntryService.create({
comments,
issue_id: issue?.id || (await this.issueService.list()).data[0]?.id,
spent_on: moment(new Date()).format("YYYY-MM-DD"),
hours: 0.01,
activity_id: this.localStorageService.get("defaultActivityId")
})
return data
}
You can probably speed this up further by parallelizing the first two async calls:
async startNewEntry() {
const [activeProjects, issue] =
await Promise.all([fetchActiveProjects(), this.createIssue()])
const timeEntry = await createTimeEntry({ issue, comments: this.state.timeEntry.comments })
this.setState({ activeProjects, issue, timeEntry })
}
If you want startNewEntry to wait to do its work until after addIssue has done its work, you need to:
Have addIssue return a promise it fulfills when it's finished its work, and
Use await when calling it: await this.addIssue();
If you need startNewEntry to see the updated state, addIssue's promise will need to be fulfilled from the state completion handler callback, like this:
addIssue() {
// *** Return the promise chain to the caller
return this.projectService.list([]).then(response => {
response.data = response.data.filter((x: any) => x.status === 1);
// *** Create a new promise
return new Promise(resolve => {
this.setState(
{
activeProjects: response.data
},
() => {
this.createIssue();
resolve(); // *** Fulfill the promise
}
);
});
});
}
Often, new Promise is an anti-pattern, particularly when you have another promise you can chain from. But in this case, since you need to wait for the callback from setState (which isn't promise-enabled), it's appropriate. (
Note my comment on the question. I think you're setting up an endless loop...
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"));
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();
I know how to emit a value to the observer and subscribe to them using observable, as shown here
var observable = new Observable(observer => {
observer.next(1);
observer.next(2);
observer.next(3);
}).subscribe((success) => {
console.log(success);
})
but if I want to do the same thing with the function, ie. I have something like this, then how can I achieve it
var observable = new Observable(observer => {
observer.next(function () {
setTimeout(() => {
1
}, 1000)
})
observer.next(function () {
setTimeout(() => {
2
}, 1000)
})
observer.next(function () {
setTimeout(() => {
3
}, 1000)
})
}).subscribe((success) => {
console.log(success);
})
is it possible, all I have to do is call a series of async functions, how can I do it
UPDATE
i want to call a series of asnc fuctions in a sequence, ie. the second should be called only after the completion of the first functions operation and so on and so forth
You can do something like this. This is just the fundamental here. You can call your async instead of emitting static values.
var ParentObservable = new Observable();
ParentObservable.subscribe((res) => {
//res is your response from async calls
//Call asyncCall again from here
})
function asyncCall(){
this.http.get("your URL").map((res)=> res.json()).subscribe((res)=>{
ParentObservable.next(res);
})
}