How to resolve a Promise only after a state change - javascript

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

Related

Returning async data from method1 to method2 while having no influence on the script that fetches the async data in method1

In TypeScript I try to call an external script SPCalendarPro in a private method that async fetches some data. The script is called as follows:
private _getSPCalendarPro() {
const spcalpro: any = require('./spcalendarpro.js');
}
After calling the script I need to call the function as follows:
spcalpro.getCalendarEvents({
listName: "StaffSchedule"
}).ready(function(data, obj) {
if (obj.error) console.error( obj.error );
console.table( data );
});
The complete method is as follows:
private _getSPCalendarPro() {
const spcalpro: any = require('./spcalendarpro.js');
return spcalpro.getCalendarEvents({
listName: "StaffSchedule"
}).ready(function(data, obj) {
if (obj.error) console.error( obj.error );
console.table( data );
return obj;
});
}
The script returns a data and obj variable, which I have to use in another method. However, when I call the above method from another function I get the .ready() function back as text. Leaving out the .ready() part returns me the fetched object, but with the data part empty. This is due to the fact that the data is fetched async and not yet resolved when the method is returned. The returned object consists of:
listName:
async:
fields:
userEnvData:
callback:
CamlQuery:
data:
The method from which I call the _getSPCalendarPro method:
private _calendarData() {
const calObj = this._getSPCalendarPro();
const calObjData = calObj['data'];
console.log(calObj);
console.log(calObjData);
}
calObj is filled nicely, but calObjData is undefined. I've tried to use async / await and jquery deferred, but without luck. I couldn't find an answer on this particular subject either. Hope someone can help me on what I am doing wrong.
Thanks.
EDIT
I have tried to create a promise for _getSPCalendarPro, but I am not sure how to do this the right way since the external script is fetching the actual data.
private _getSPCalendarPro(): Promise<void> {
const spcalpro: any = require('./spcalendarpro.js');
const spcal = spcalpro.getCalendarEvents({
listName: "StaffSchedule"
}).ready(function(data, obj) {
if (obj.error) console.error( obj.error );
console.table( data );
return obj;
});
return spcal().then((response) => {
return response;
})
}
FINAL WORKING CODE
Thanks to Yoshi.
export interface ISPCalendarPro {
data: any;
}
private _getSPCalendarPro(): Promise<ISPCalendarPro> {
const spcalpro: any = require('./spcalendarpro.js');
return new Promise((resolve, reject) => {
const spcal = spcalpro.getCalendarEvents({
listName: "StaffSchedule"
}).ready(function(data, obj) {
obj.error
? reject(obj.error) // reject on error
: resolve(obj); // or resolve
})
})
}
private _calendarData() {
this._getSPCalendarPro().then((calData) => {
console.log(calData);
});
}
I think you can return a promise and resolve it using the supplied ready callback.
Something like:
type Data = unknown; // you'll need to fill this in.
class Something
{
private _getSPCalendarPro(): Promise<Data> {
const spcalpro: any = require('./spcalendarpro.js');
// return a promise
return new Promise((resolve, reject) => {
// on ready
spcalpro.getCalendarEvents({listName: "StaffSchedule"}).ready(function(data, obj) {
obj.error
? reject(obj.error) // reject on error
: resolve(data); // or resolve
});
});
}
private async _calendarData() {
try {
// await the promise
const data = await this._getSPCalendarPro();
}
catch (error) {
console.error(error);
}
}
}

Undefined value returned - Javascript

I am receiving a value from a function to pass to a method in a class. When I pass the value returned from the function in web.js, it returns undefined in my shopifyStore class. What could be the reason and how I could solve this ?
PS: Beginner with javascript
web.js
window.shopify.findName({}, function(items) {
var shopify = items;
console.log(items); //this returns the value
pushValue(items);
});
export function pushValue(items) { return items; }
component
import * as shopifyHelper from '/web.js';
class shopifyStore extends HTMLElement {
constructor() {
super();
}
getItemCount() {
console.log(shopifyHelper.pushValue()) // this returns undefined
}
}
You should promisify the callback of window.shopify.findName method. Rework your pushValue function:
export function pushValue(items) {
return new Promise((resolve, reject) => {
window.shopify.findName({}, items => resolve(items));
})
}
and call it like this:
async getItemCount() {
console.log(await shopifyHelper.pushValue());
}
or:
getItemCount() {
shopifyHelper.pushValue().then(items => console.log(items));
}
Here you can read more about async functions.
And here you can read more about promises.

Avoid loading event chains in javascript

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 */)

Recursive promise not resolving

I have a function that fetches some data. If data is not available (it becomes available after some short time), it returns null. I created a function, that returns promise, which is wrapped around logic that fetches data and checks if it is is available - if not, it calls itself:
Foo.prototype.fetchDataWrapper = function () {
return new Promise((resolve, reject) => {
const data = fetchData();
if (data) {
resolve(data)
} else {
setTimeout(() => {
return this.fetchDataWrapper()
}, 100)
}
})
}
Problem is, that despite data is fetched correctly, this promise never resolves. What am I doing wrong?
Your return this.fetchDataWrapper() is returning from the timer callback, no fetchDataWrapper. fetchDataWrapper has already returned. Instead, pass that promise into resolve:
Foo.prototype.fetchDataWrapper = function () {
return new Promise((resolve, reject) => {
const data = fetchData();
if (data) {
resolve(data);
} else {
setTimeout(() => {
resolve(this.fetchDataWrapper()); // *****
}, 100);
}
})
};
When you pass a promise to resolve, it makes the promise resolves belong to resolve or reject based on the promise you pass to it.
(I've also added some missing semicolons to that code. I recommend being consistent with semicolons: Either rely on ASI or don't, both don't mix the two. Also recommend not relying on ASI, but that's a style choice.)
Side note: It would probably make sense to reject after X attempts, perhaps:
Foo.prototype.fetchDataWrapper = function (retries = 5) {
// *** Declare retries param with default -^^^^^^^^^^^
return new Promise((resolve, reject) => {
const data = fetchData();
if (data) {
resolve(data);
} else {
if (retries) { // *** Check it
setTimeout(() => {
resolve(this.fetchDataWrapper(retries - 1));
// *** Decrement and pass on -^^^^^^^^^^^
}, 100);
} else {
reject(); // *** Reject
}
}
})
};

Deferred chains in jquery

I need to make 3 requests in chain. So for this I use jquery deffered.
Request 1
-> on done if response contains expected result then Request 2 else return empty array/null
-> on done if response contains expected result then Request 3 else return empty array/null
private request1() {
const vm = this;
vm.isLoading(true);
let deffer = system.defer(dfd => {dataService.getResponse1()
.done((response) => {
request2(response.collection))
dfd.resolve();
});
return deffer.promise();
}
private request2(collection) {
dataService.getResponse2(collection)
.done((response) => request3(response.collection));
}
private request3(collection) {
dataService.getResponse3(collection)
.done((response) => saveResponse(response.collection));
}
private saveResponse(collection) {
//do some stuff
}
in Constructor I call request1 like
vm.request1().done(() => {
vm.isLoading(false);
});
The problem is that isLoading is setted to false before saveResponse is called. How should I correctly refactor my requests-structure to update isLoading after all requests are finished?
Thanks.
Try this way (please check comments in the code):
// Request 1 -> on done Request 2 -> on done -> Request 3
private request1() {
const vm = this;
vm.isLoading(true);
let deffer = system.defer(dfd => {dataService.getResponse1()
.done((response) => {
// 1. Here you have to resolve the dfd promise after
// request2 promise is resolved. For this reason,
// I added the call to "done()" method.
request2(response.collection)).done((response2) => { dfd.resolve()});
});
return deffer.promise();
}
private request2(collection) {
// 2. You need to return the promise returned by getResponse2
return dataService.getResponse2(collection)
.done((response) => request3(response.collection));
}
private request3(collection) {
// 3. You need to return the promise returned by getResponse3
return dataService.getResponse3(collection)
.done((response) => saveResponse(response.collection));
}
private saveResponse(collection) {
//do some stuff
}
So, in request3() you return the promise returned by getResponse3() which, in turn, return the promise returned by saveResponse() called inside the done() method.
In request2() you return the promise returned by getResponse() which, in turn, returns the promise returned by request3() described in the previous paragraph.
In request1(), in the main done() callback, you call request2() and wait (using done()) it finishes before resolve the main promise.
In this way, vm.isLoading(false) should be called when request2 and request3 have been completed.
Short answer: move vm.isLoading(true); inside the body of request3, after you call saveResponse.

Categories

Resources