I have a problem in my code where many entities have async loading procedures and can't be used until these are complete. There are chains of these dependencies.
So A->B->C where A needs B needs C.
I have written code like
class B{
constructor(callback){
this.loaded=false
this.load(callback)
}
load(callback){
...do stuff
this.loaded=true
callback()
}
}
class A{
constructor(){
this.loaded=false
this.b=new B(()=>{this.loaded=true})
}
}
This seems really bad. Can anyone suggest a better solution?
Usually, it is a bad practice to perform async task directly in constructor (as stated here). With this taken in account, you can follow VLAZ advice and start using promises.
You would then have something like this:
class B {
constructor() {
this.loaded = false
}
load() {
return new Promise((resolve => {
this.loaded = true;
// do stuff
return resolve()
}))
}
}
class A {
constructor() {
this.loaded = false
}
load() {
return new Promise(resolve => {
this.b = new B()
this.loaded = true
return resolve(this.b)
})
}
}
// Use it like this
const a = new A()
a.load()
.then(b => b.load())
.then(/* and so on */)
Related
I'm attempting to introduce a queue-type system to a JS class to allow for Async method chaining, ideally, I'd like to perform operations on the class instance using these async methods and return "this" instance.
export class Queue {
constructor() {
this.queue = Promise.resolve()
this.firstRequestStarted = false
this.firstRequestStatusCode = 0
this.secondRequestStarted = false
this.secondRequestStatusCode = 0
}
then(callback) {
callback(this.queue)
}
chain(callback) {
return this.queue = this.queue.then(callback)
}
first() {
this.chain(async () => {
try {
this.firstRequestStarted = true
const response = await axios.get("https://stackoverflow.com/questions")
this.firstRequestStatusCode = response.status
return this
}
catch (e) {
const { message = "" } = e || {}
return Promise.reject({ message })
}
})
return this
}
second() {
this.chain(async () => {
try {
this.secondRequestStarted = true
const response = await axios.get("https://stackoverflow.com/")
this.secondRequestStatusCode = response.status
return this
}
catch (e) {
const { message = "" } = e || {}
return Promise.reject({ message })
}
})
return this
}
}
Functions are added to the queue, and as we await them, the "then" method will handle their execution.
const x = await new Queue()
.first()
.second()
console.log(x)
The challenge I'm facing is that I can never actually get "this" (instance of Queue) back to x.
1) x === undefined
2) "Chaining cycle detected for promise #<Promise>"
or ( I haven't been able to track down where this one is coming from, node error)
3) finished with exit code 130 (interrupted by signal 2: SIGINT)
I have tried adding a "consume" method, which simply returns "this", this leads to error #2 above
me() {
this.chain( () => {
try {
return this
}
catch (e) {
const { message = "" } = e || {}
return Promise.reject({ message })
}
})
return this
}
The confusion on my part, is that if I use any value other than "this", it works as expected
me() {
this.chain( () => {
try {
return "test"
}
catch (e) {
const { message = "" } = e || {}
return Promise.reject({ message })
}
})
return this
}
x === "test"
I'm also able to return the values associated to this with something like the following
return {...this}
Ideally, I'd like to return the instance of Queue to X, as I plan on modifying the properties of the Queue instance through my async methods, await them, and be returned with an "initialized" instance of Queue.
Any input would be greatly appreciated - thank you!
The problem is that your Queue instances are thenable (have a .then() method), and the promise is tried to be resolved with itself (this.queue). See also here or there.
You have two options:
Do not resolve your promise with the instance, but write
const x = new Queue().first().second();
await x;
console.log(x);
remove the then method from your class, then call
const x = new Queue().first().second().queue;
console.log(x);
(or possibly introduce a getter method - .get(), .toPromise() - instead of directly accessing .queue)
I have a click that calls the method:
public clickEvent() {
this.createIframe().then((iframe) => { // Return iframe or create if is not before needed inside
// Async hard logic here
})
}
Problem is when user clicks a lot of times clickEvent() it fires promise and then fires a hard logic inside.
How to avoid click until logic inside is not finished?
Or disable to call logic inside if it is done?
If you're using Angular, I think you can convert the click event into an observable and then use the variety of operators such as exhaustMap to achieve this.
import { exhaustMap, fromEvent } from 'rxjs';
....
#ViewChild('btnId') btnElementRef!: ElementRef<HTMLButtonElement>;
ngAfterViewInit(): void {
fromEvent(this.btnElementRef.nativeElement, 'click')
.pipe(
exhaustMap(() => this.createIframe())
)
.subscribe((iframe) => {
// hard coded async logic here
});
}
);
This will ignore sub-sequent click until the Promise resolve first.
Further more, if you want to disable the button and display somekind of loading indicator, you can also add a variable to track that inside the stream using tap
fromEvent(this.btnElementRef.nativeElement, 'click')
.pipe(
tap(() => isProcessing = true),
exhaustMap(() => this.createIframe())
)
.subscribe((iframe) => {
isProcessing = false;
// hard coded async logic here
});
Make createIframe cache its Promise (like as an instance property), and return that first if it exists, instead of starting another. For example:
// example function that creates the Promise
const createPromise = () => {
console.log('creating Promise');
return new Promise(resolve => setTimeout(resolve, 3000));
}
class SomeClass {
createIframe() {
if (this.iframePromise) return this.iframePromise;
this.iframePromise = createPromise();
return this.iframePromise;
}
clickEvent() {
this.createIframe().then((iframe) => {
console.log('clickEvent has received the Promise and is now running more code');
})
}
}
const s = new SomeClass();
button.onclick = () => s.clickEvent();
<button id="button">click to call clickEvent</button>
If you also want to prevent // Async hard logic here from running multiple times after multiple clicks, assign something to the instance inside clickEvent instead.
// example function that creates the Promise
const createPromise = () => {
console.log('creating Promise');
return new Promise(resolve => setTimeout(resolve, 3000));
}
class SomeClass {
createIframe() {
return createPromise();
}
clickEvent() {
if (this.hasClicked) return;
this.hasClicked = true;
this.createIframe().then((iframe) => {
console.log('clickEvent has received the Promise and is now running more code');
})
}
}
const s = new SomeClass();
button.onclick = () => s.clickEvent();
<button id="button">click to call clickEvent</button>
I am trying to resolve a Promise only after a certain condition is met outside the promise:
class myClass extends ... {
render() {
...
this.fetch();
....
}
fetch(){
Promise.all([
....,
....,
....
]).then( () => {
// new Promise or somthing like that
// wait for state changed to resolve it
}
).then(
// do something
)
}
stateChanged(state){
if(condition){
// resolve promise from here
}
}
}
I tried to implement it in similar way to this question
Resolve Javascript Promise outside function scope
but I do not understand
how we can call a variable
how to make this work inside my Class
I tried it with a this.waitForPromise = null variable, but when I call
it later this.waitForPromise() I get a is not a function Type error.
class myClass extends ... {
render() {
...
this.fetch();
....
}
constructor(){
this._waitForPromise = null;
}
fetch(){
Promise.all([
....,
....,
....
]).then( () => {
new Promise(function(resolve, reject) {
this._waitForPromise = resolve;
});
}
).then(
// do something
)
}
stateChanged(state){
if(condition){
this._waitForPromise();
}
}
}
Any help and explanation is much appreciated.
You have a closure issue using this, so capture it first.
You should return the promise in your then.
You should check to see if promise is created when state changes.
Keep in mind you can only invoke one of resolve/reject, and only once.
class myClass extends ... {
render() {
...
this.fetch();
....
}
constructor(){
this.resolve = null;
this.state = null; // TODO initial state
}
fetch(){
const that = this;
Promise.all([
....,
....,
....
]).then( () => {
return new Promise(function(resolve, reject) {
that.resolve = resolve;
that.checkState();
});
}
).then(
// do something
)
}
checkState(){
if(condition && this.resolve){
this.resolve();
this.resolve = null; // set to null to ensure we don't call it again.
}
}
stateChanged(state){
this.state = state;
this.checkState();
}
}
OK, so I'm not all that familiar with Polymer 3, but I'm fluent in Polymer 1 and 2.
I'm getting that you want to do this:
Get an ajax promise.
Return said ajax promise once a certain state has changed.
do a fetch call if said promise hasn't been done.
What you need to do is to save your promise as a variable, and then just chain it together with other method calls. The comments below explains more about what I'm doing with your example code from the OP.
class myClass extends ... {
render() {
...
this.fetch();
....
}
fetch(){
this.set('savedPromise', Promise.all([ // save promise to variable
....,
....,
....
])
// not needed
/* .then( () => {
// new Promise or somthing like that
// wait for state changed to resolve it
}*/
).then(
// do something
return result // added this line
)
}
stateChanged(state){
if(condition){
if (this.savedPromise) {
this.savedPromise.then(function(response) {
// handle data
});
else {
fetch().then(function(response) { // if set before render(), do a fetch call.
// handle data
}
}
}
}
}
I have a simple class
class A{
constructor(){
this.loadComponents().then(function(values) {callbackOnLoad();});
}
callbackOnLoad(){
//do some things
}
loadComponents(){
...
return Promise.all([p1,p2,p3,p4,p5,p6,p7,p8]);
}
}
I am unable to call callbackOnLoad after all promises are fulfilled. I know that "this" depends on the caller and so I understand why callbackOnLoad does not work. How can I solve this problem? How do I have to structure/design my code?
The proper way to do it would be calling then & catch immediately after Promise.all.
class A{
constructor() {
this.loadComponents();
}
callbackOnLoad = () => {
//do some things
}
loadComponents = () => {
return Promise.all([p1,p2,p3,p4,p5,p6,p7,p8]).then((values) => {
this.callbackOnLoad();
}).catch((error) => {
console.log(error);
});
}
}
I am implementing Pusher into my React+Redux Saga application, but I am having a few problems with some callbacks where I can not hit the put(...) methods. Using console.log(...) etc. in the methods does show, but I am not able to put to the state of my application.
I could be wrong on some of the implementation of async/generator functions, but I am pretty much stuck right now.
My code to illustrate what will not fire:
import { takeLatest } from 'redux-saga'
import { call, put } from 'redux-saga/effects'
// Pusher actions
export const pusherConnecting = () => {
return {
type: ActionTypes.PUSHER_CONNECTING
}
};
export const pusherConnectSucceeded = (client) => {
return {
type: ActionTypes.PUSHER_CONNECT_SUCCEEDED,
client: client
}
};
const pusherConnectFailed = (exception) => {
return {
type: ActionTypes.PUSHER_CONNECT_FAILED,
message: exception
}
};
// Pusher Saga
function * connectPusher(action) {
try {
const pusher = yield call(Api.connectPusher, action.directory, function(subscription) {
subscription.bind(PUSHER_BIND_RELOAD, function() {
location.reload(true);
});
subscription.bind(PUSHER_BIND_REQUEST_DATA, function(data) {
if (data) {
put(updateDirectory(data));
} else {
put(requestDirectory(action.directory.id));
}
});
});
pusher.connection.bind('connected', function() {
put(pusherConnectSucceeded(pusher));
});
yield put(pusherConnecting());
} catch (e) {
yield put(pusherConnectFailed(e));
}
}
export default function * pusherSaga() {
yield * takeLatest(ActionTypes.DIRECTORY_FETCH_SUCCEEDED, connectPusher);
}
// My Api.ConnectPusher
export function * connectPusher(directory, subscription) {
var pusher = new Pusher(PUSHER_KEY, {
encrypted: true
});
var channels = ["test1", "test2" ];
for (var i = 0; i < channels.length; i++) {
// Take each channel and callback with the subscription
yield subscription(pusher.subscribe(channels[i]));
}
return pusher;
}
Solution based on #Sebastien
yield put(yield onConnect(pusher));
function onConnect(pusher) {
return new Promise((resolve, reject) => {
pusher.connection.bind('connected', function() {
resolve(pusherConnectSucceeded(pusher));
});
});
}
Redux-saga does not permit to put without using keyword yield. The put creates a simple json object/effect that must be interpreted/executed, and it won't if you don't yield.
Also, even with yield put(...), if this is done in a callback, it won't be interpreted, because Redux-saga does not have the ability to run callbacks in its interpreter. They'll simply be run as normal callbacks and nothing will happen.
If subscription.bind is supposed to return a single result, you can instead wrap that call into a function that returns a promise, and then yield that promise.
If subscription.bind is supposed to return a stream of results, you might need instead of create a channel. I guess in the future someone will ship something that can easily permits to transform Observables to Redux-saga streams
Note that if you don't need to unsubscribe/resubscribe multiple times, it may be simpler to you to put this code outside the saga, and just do
subscription.bind(PUSHER_BIND_RELOAD, function() {
location.reload(true);
});
subscription.bind(PUSHER_BIND_REQUEST_DATA, function(data) {
if (data) {
reduxStore.dispatch(updateDirectory(data));
} else {
reduxStore.dispatch((requestDirectory(action.directory.id));
}
});