Promise as a class method call triggers object.then upon resolving - javascript

I have an class that contains db methods and it's wrapped in a proxy which handles access to properties. Since the issue is related to promises here is an simplified example code that is reproducing the same issue:
const handler = {
ownKeys(target) {
return Object.keys(target._attributes)
},
get(target, property) {
console.log(`<-${property}`) // <-- this logs what properties are being accessed
if (typeof target[property] !== 'undefined') {
return Reflect.get(target, property)
}
return Reflect.get(target._attributes, property)
},
set(target, property, value) {
target._attributes[property] = value
return true
}
}
class User {
static table = 'users'
static fetch(query = {}, opts = {}) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(new this(query))
}, 500)
})
}
constructor(attributes = {}) {
this._attributes = attributes
return new Proxy(this, handler)
}
}
async function trigger() {
const user = await User.fetch({test:'test'})
console.log(JSON.stringify(user._attributes))
}
trigger()
Everything works well, during the testing I've added a printout to the proxy to determine performance hit of using such model design, and I noticed that my model get's called from within promise chain.
Example output follows:
<-then
<-_attributes
{"test":"test"}
I guess that returning new this(query) causes the promises to think that maybe it's a promise returned and consequently .then() is executed.
Only workaround that I've found is to wrap resolve response inside new array or another object like this:
static fetch(query = {}, opts = {}) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve([new this(query)])
}, 500)
})
}
// Output
// 2
// <-_attributes
// {"test":"test"}
What I'm wondering is, is this correct way and are there other solutions to this side effect?

All objects passed to as a result of a promise is always checked to see if it has a then property. If it does, that function is used to queue up entries to get a final value. This is why logic like
Promise.resolve()
.then(() => {
return Promise.resolve(45);
})
.then(result => {
console.log(result);
});
logs 45 instead of a promise object. Since the promise objects has a .then property, it is used to unwrap the promises value. The same behavior happens in your resolve(new this(query)) case, because it needs to know if there is a value to unwrap.
As you've said, commented in your post, you could certainly wrap the instance with a non-proxy, e.g.
resolve({ value: new this(query) })
which would check for .then on that object instead of on your proxy, but then you have to do .value to actually get the proxy, which can be a pain.
At the end of the day, that is a choice you'll have to make.

Related

How do we retrieve a Boolean with exists in mongoose?

I am trying to check weather an account associated with the same username already exists or not. I am using the exist method to check but I keep getting a large object instead of a Boolean value.
async checkExisting(username,userCollection) { //WORK ON ISSUE WITH VERIFYING
const check = new Promise((resolve,reject) => {
let value = userCollection.exists({username})
console.log(value);
// if(userCollection.exists({username})) {
// reject("Username taken")
// }
resolve("Username avaliable")
})
return check;
},
Your code is correct. It's just what you write in resolve is returned.
And no need to make the function async as you're already returning a Promise. So where you call this function there just keep await as follows
await checkExisting('username', 'collection')
checkExisting(username, userCollection)
{
return new Promise((resolve, reject) => {
userCollection
.exists({username})
.then((value) => {
if (value) {
resolve(true)
}
resolve(false)
})
.catch((err) => reject(err))
})
}
Note: You can use either promise or async-await syntax; both ways are correct. However, never combine these two concepts as it will give unexpected output.
userCollection.exists({username}) returns a query that you never ran. You need to actually execute it and wait for the result. Also avoid the Promise constructor antipattern. Just do
async checkExisting(username,userCollection) {
const check = await userCollection.exists({username})
if (check) {
throw new Error("Username taken");
}
return "Username avaliable";
},

How to stack promises dynamically?

I have a nested object subprojects with a property of type array: userEstimates holding object(s) Estimate.
I am looking to iterate through userEstimates and push a fetch/promise to an array without calling it.
main (inside async function)
await subproject.getUserEstimates(true);
let stack = [];
subproject.userEstimates.forEach(ue =>
stack.push(ue.fetchAvailableRates)
);
console.log(stack); // 3) [ƒ, ƒ, ƒ]
await Promise.all(stack);
fillForm(subproject);
however, attributes on subproject are not defined when calling fillForm
function definition for fetchAvailableRates:
fetchAvailableRates = () => {
this.availableRates = fetch(...)
.then((resp) => {
if (resp.ok) return resp.json();
else throw new Error("Something went wrong");
})
.then((r) => {
...
return {
Rate.get(), // returns class
...
};
})
.catch((e) => {
console.error(e);
});
};
EDIT: Changed my wording frrom queue to stack as i'm trying to run all requests at once, and I don't care about order
Your definition for fetchAvailableRates uses this, but when you refer to an object's function without calling it (like stack.push(ue.fetchAvailableRates)), it loses the reference to this and simply becomes a function.
To refer to a function that calls ue.fetchAvailableRates on the ue instance at the time, you should call either () => ue.fetchAvailableRates() or ue.fetchAvailableRates.bind(ue).
That's not the only change you'd need to make―Promise.all() doesn't accept functions, only promises, so the right call is probably to make ue.fetchAvailableRates() return the Promise and add that to the stack.

Can you add a .then to a promise after it's created?

Promises just baffle me.
I'm trying to make a mock data service to imitate axios.
My mock put call passes a targetUrl to _fetch which then sees if it's a valid url and either returns a new Promise with a delayed .resolve
const _returnResponse = (mockData, time = 0) => new Promise((resolve) => {
setTimeout(() => {
resolve(mockData);
}, time);
});
or a new Promise with a delayed .reject
const _returnError = (time = simulatedDelay) => {
const returnValue = new Promise(((resolve, reject) => {
setTimeout(() => {
reject(new Error('error'));
}, time);
}));
return returnValue;
};
but when I make my mock put call this returns a mock data that the calling method interprets as a success and console logs in its .then
put(target, putBody) {
const returnValue = _fetch(target, simulatedDelay)
returnValue.then(response => _console('PUT', target, response, putBody));
return returnValue;
},
But with an invalid target console logs an uncaught error
or this handles the error correctly, but console logs an undefined response
put(target, putBody) {
const returnValue = _fetch(target, simulatedDelay).then(response => _console('PUT', target, response, putBody));
return returnValue;
},
Here's the calling method:
saveStuff({ commit, state }, newStuff) {
//other code
return this.$mockAxios.put(url, putBody)
.then((response) => {
return response;
});
},
I feel like I'm completely missing something and I've researched this for hours and I'm still not getting it.
As a direct answer to the question: yes, you can add .then() to a promise after it's created.
Example:
const hi = new Promise((resolve, reject) => {
setTimeout(() => resolve('hello'), 2000);
});
hi.then(result => console.log(result));
As for promises baffling you, I would recommend (aside from more reading) just playing around a lot in your IDE with setTimeout. I know you're already using setTimeout in your mock, but strip it down further and just run the code in its own file, so that you control the whole environment. With lots of console.log('blah') to see the order and such. Also ensure you're just as familiar with callbacks, as promises are just syntactic sugar for callbacks. Try to read up on the JS event loop at the same time - it might provide some context if you know how and when a callback/promise executes.
Note that you can even add .then() after the callback has resolved or rejected. Hence the term "promise" - it's literally a promise that your .then() function will run. https://en.wikipedia.org/wiki/Promise_theory
const hi = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('one second');
resolve();
}, 1000);
});
setTimeout(() => {
hi.then(() => console.log('two seconds. this executes approximately a full second after the first promise has resolved'));
}, 2000);

How to pass information around when working with JavaScript Promise

At one point, we are calling our backend several times with fetch. Each call returns a promise, which we collect and pass to our responseHandler. This handler is supposed to dispatch redux actions when each response comes in. The action will be shaped differently based on two factors:
the response code of the server
the content of the request
so we have this
function save(objects) {
let promises = objects.map(object => {
let promise = fetch(url, {..., body: object});
});
responseHandler(promises);
}
function responseHandler(promises) {
for (let promise of promises) {
promise
.then(r => {
return r.json();
})
.then(body => dispatchSomeAction(body));
}
}
So far so good. But now we need info from inside of the save and from inside of the anonymous function that receives the response object. So I came up with the following approach. But it feels wrong to piggyback on the promise object. I can't say why.
function save(objects) {
let promises = objects.map(object => {
let promise = fetch(url, {..., body: object});
promise._objectType = object._type;
});
responseHandler(promises);
}
function responseHandler(promises) {
for (let promise of promises) {
promise
.then(r => {
promise._status = r.status;
return r.json();
})
.then(body => dispatchSomeAction({
status: promise._status,
type: promise._type, ...body
}));
}
}
As can be seen, every time I want a value of a scope to be available later, I place it on the promise object because that promise object is in all the other scopes. Is there a cleaner way?
Note: The backend returns an object that doesn't hold the _type property (actually I clear my objects of those flags before sending them to the backend. It's a frontend flag that I still require when the answer comes)
You must carry the object. Using ES6 structuring and destructuring is not very clumpsy:
function save(objects){
let promises = objects.map(object => {
return fetch(url, {..., body: object}).then(r => ({r,type:object._type}));
}
responseHandler(promises)
}
function responseHandler(promises){
for (let promise of promises){
promise
.then(({r,type}) => {
return r.json().then(body => dispatchSomeAction({status: r.status, type, ...body));
});
}
}
Note that you don't really need to do anything at all with the status, as r it was still in scope where it's used.
I would try something like the following
function save(objects){
let promises = objects.map(object) =>{
return fetch(url, {..., body: object}).then(r=>{
return {data: r, originalObject: object}
})
}
responseHandler(promises)
}
function responseHandler(promises){
for (let promise of promises){
promise
.then(r => {
return {data: r.data.json(), originalObject: r.originalObject)
.then(r=> dispatchSomeAction({status: r.data.status, type: r.originalObject.type, ...r.data.body));
}
}
you can tweak a little bit, say, without passing the whole object, just the type field.
A common pattern is to use a single global object (commonly called state or appData or similar) to store values and allow you to access them from within different closures.
Here's a contrived example of using this pattern to load a bunch of items, and then to reload the data for each item after clicking a "refresh" button:
const state = {}
getItemsPromise().then(response => {
state.items = response.items
})
$(refreshItemsButton).on('click', ()=>{
state.items.forEach(item => {
refreshItemPromise(item).then(response => {
state.items[item.id] == response.item
})
})
})

Promise.all to resolve all the promise even if some failed to update the redux store [duplicate]

This question already has answers here:
Wait until all promises complete even if some rejected
(20 answers)
Closed 5 years ago.
I am using API call to get data and update my redux store. Following is state changes for each API call.
Before making API call, set isLoading as true
On success reset isLoading as false
On failure reset isLoading as false and set isError as true.
My component needs data from three APIs which is stored in three different redux stores as below structure.
store: {
book: {
isLoading: false,
isError: false,
data: {}
},
teacher: {
isLoading: false,
isError: false,
data: {}
},
}
In my component, I use following to call api
componentWillMount() {
const {
loadBook,
loadTeacher,
} = this.props;
// all these load functions dispatch action which returns Promise for async API call using `fetch`
const apiCalls = [
loadBook(),
loadTeacher(),
];
Promise.all(apiCalls);
}
I have written selector to check the loading state as below.
export const getIsLoading = createSelector([
getIsBookLoading,
getIsTeacherLoading,
],
(bLoading, tLoading) => (
bLoading || tLoading
)
);
Based on value of getIsLoading I do show loading state in my component otherwise render component.
However I see problem happens when one of the API call fails. For example, loadBook fails in 100 ms and that time bLoading is changed back to false however tLoading still is true bcz loadTeacher api calls was not finished. Since Promise.all() do either all or nothing therefore API call for loadTeacher never finished therefore tLoading stas true.
Is there a way to let Promsie.all to resolve all the calls even if some failed so that it can clean dirty state?
If loadbook fails then loadTeacher won't stop, your resolve handler of Promise.all simply isn't called.
You can see in the following code that both tLoading (set false in resolve) and bLoading (set in catch) are false:
var bLoading = true;
var tLoading = true;
const loadBook =
() => Promise.reject("load book rejects");
const loadTeacher =
() => Promise.resolve("load book rejects")
.then(()=>tLoading=false);
Promise.all([
loadBook()
.catch(
(err)=>{
bLoading=false;
return Promise.reject(err);
}
),
loadTeacher()
])
.catch(
()=>console.log("bLoading:",bLoading,"tLoading:",tLoading)
);
Cannot insert as snipped because that is buggy as hell in Stack Overflow and keeps failing but you can run the code in the console.
Ash Kander is on the right track if you want to use Promise.all without failing but how do you distinguish between valid resolve values and rejects? It's better to make an explicit fail value:
const Fail = function(reason){this.reason=reason;};
Promise.all(
[
loadBook,
loadTeacher
].map(
fn=>
fn()
.catch(
(err)=>
new Fail(err)
)
)
)//this will never fail
.then(
([book,teacher])=>{
console.log("did book fail?",(book&&book.constructor===Fail));
console.log("did teacher fail?",(teacher&&teacher.constructor===Fail));
}
)
You have to either use a dedicated library (there are some, I dont remember the names -_-) or do it yourself - which is not so hard.
I do have some code doing it:
var MyPromise = {};
MyPromise.getDefaultPromise = function () {
return new Promise(function (resolve, reject) { resolve(); });
};
MyPromise.when = function (promises) {
var myPromises = [];
for (var i = 0; i < promises.length; i++) {
myPromises.push(MyPromise.reflect(promises[i]));
}
return Promise.all(myPromises).
then(function (oResult) {
var failure = oResult.filter(function (x) { return x.status === 'error';});
if (failure.length) {
return MyPromise.fail(failure);
}
return MyPromise.getDefaultPromise();
});
};
MyPromise.fail = function (failure) {
return new Promise(function (resolve, reject) {
reject(failure);
});
};
MyPromise.reflect = function (promise) {
return new Promise(function (resolve) {
promise.then(function (result) {
resolve(result);
}).
catch(function (error) {
resolve(error);
});
});
};
Calling MyPromise.when(-your promises array-) will ALWAYS resolve, and send you an array containing the failing promises that you can analyze in the 'then'

Categories