Promise not resolving to value when returned - javascript

I have a recursive function that resolve a mapped array correctly recursively.
const postCommentsRecursive = (author, permlink) => {
return client.database.call('get_content_replies', [author, permlink])
.then(replies => Promise.all(replies.map(r => {
if (r.children > 0) {
return postCommentsRecursive(r.author, r.permlink)
.then(children => {
r.replies = children;
return r;
})
}else {
return r;
}
})))
}
But when I added another data fetch, the return from then stays a promise:
const postCommentsRecursive = (author, permlink) => {
return client.database.call('get_content_replies', [author, permlink])
.then(replies => Promise.all(replies.map(r => {
//NEW CODE START
const votes = client.database
.call('get_active_votes', [r.author, r.permlink])
.then(av => {
return av;
});
r['active_votes'] = votes;
//NEW CODE END
if (r.children > 0) {
return postCommentsRecursive(r.author, r.permlink)
.then(children => {
r.replies = children;
return r;
})
}else {
return r;
}
})))
}
av is the array value, great. But when I add it to the object from votes, it is a Promise instead.
Each replies child object is still resolved as a value, but inside each r when I add votes to r as r['active_votes'] = votes, the value set in the object is a Promise {<resolved>: Array(1)}.
I can't figure out how to get the actual Array(1) as a value, not the Promise as a value. How do I just get the Array as a value, and not the Promise?
Thank you!

In your code, you're assigning r.votes to be a Promise. Instead, you need to go into the .then of that promise if you want to mutate r. (Conceptually, it's the same as what you're doing in the r.children > 0 block)
return client.database
.call('get_active_votes', [r.author, r.permlink])
.then(av => {
// mutate r
r.active_votes = av;
// now return it so that this entire promise resolves with `r`
return r;
});
async/await generally makes code like this a little clearer:
r.active_votes = await client.database
.call('get_active_votes', [r.author, r.permlink])

Related

Promise.All returning empty

I have few api call requests which i am trying to create promises and then execute all using Promise.all but, its giving empty value instead of array. Below is my code.
function getUser(arrUser) {
var student = [];
return new Promise((resolve, reject) => {
if (arrUser.length > 0) {
var promises = arrUseridRequest.map(userRequeset => {
return getRequest(userRequeset).then(result => {
student.push(JSON.parse(result.body).result[0].Name);
console.log(student); //This is giving right details.
}).catch(error => {
reject(error);
});
});
Promise.all(promises).then(StuName => {
resolve(StuName.join());
})
}
});
}
and this is how i am trying to get the values at once:
getUser('123').then(student => {
console.log(Student) //Coming as empty
});
getRequest is my api call nodeJs request module. What's wrong in my code?
All your promises fulfill with the value undefined since you're just logging the student names, but not returning them from the then callback. As you seem to be doing only a single request, the array will be [undefined], which is joined into the empty string.
Also avoid the Promise constructor antipattern:
function getUsers(arrUser) {
const promises = arrUser.map(userId => {
return getRequest(userId).then(result => {
const students = JSON.parse(result.body).result;
return students[0].Name;
});
});
return Promise.all(promises);
}
getUsers(['123']).then(studentNames => {
console.log(studentNames);
console.log(studentNames.join());
}).catch(console.error);

Simple Promise and Then implementation

Recently I was shown a piece of code that were asked during a full-stack developer interview.
It involved creating a Promise, in which the candidate should implement,
passing it a resolve function, and chaining 2 then's.
I tried implementing the Promise very naively only to make the code work.
Created a ctor that accepts a resolver func,
Created a Then function that accepts a callback and returns a Promise,
and simply calls the callback on the resolver function.
class MyPromise {
constructor(resolver) {
this.resolver = resolver;
}
then(callback) {
const result = new MyPromise(callback);
this.resolver(callback);
return result;
}
}
promise = new MyPromise(
(result) => {
setTimeout(result(2), 500);
});
promise.then(result => {
console.log(result);
return 2 * result;
}).then(result => console.log(result));
The expected result is 2,4 - just like operating with real Promise.
But i'm getting 2,2.
I'm having trouble figuring out how to get the return value for the first "then" and passing it along.
Here's the shortened code for creating a promise class,
class MyPromise {
constructor(executor) {
this.callbacks = [];
const resolve = res => {
for (const { callback } of this.callbacks) {
callback(res);
}
};
executor(resolve);
}
then(callback) {
return new MyPromise((resolve) => {
const done = res => {
resolve(callback(res));
};
this.callbacks.push({ callback: done });
});
}
}
promise = new MyPromise((resolve) => {
setTimeout(() => resolve(2), 1000);
});
promise.then(result => {
console.log(result);
return 2 * result;
}).then(result => console.log(result));
Your question has some issues:
The r2 variable is nowhere defined. I will assume result was intended.
The setTimeout is doing nothing useful, since you execute result(2) immediately. I will assume setTimeout(() => result(2), 500) was intended.
If the code was really given like that in the interview, then it would be your job to point out these two issues before doing anything else.
One issue with your attempt is that the promise returned by the then method (i.e. result) is never resolved. You need to resolve it as soon as the this promise is resolved, with the value returned by the then callback.
Also, the promise constructor argument is a function that should be executed immediately.
In the following solution, several simplifications are made compared to a correct Promise behaviour.
It does not call the then callbacks asynchronously;
It does not support multiple then calls on the same promise;
It does not provide the rejection path;
It does not prevent a promise from resolving twice with a different value;
It does not deal with the special case where a then callback returns a promise
console.log("Wait for it...");
class MyPromise {
constructor(executor) {
executor(result => this.resolve(result));
}
resolve(value) {
this.value = value;
this.broadcast();
}
then(onFulfilled) {
const promise = new MyPromise(() => null);
this.onFulfilled = onFulfilled;
this.resolver = (result) => promise.resolve(result);
this.broadcast();
return promise;
}
broadcast() {
if (this.onFulfilled && "value" in this) this.resolver(this.onFulfilled(this.value));
}
};
// Code provided by interviewer, including two corrections
promise = new MyPromise(
(result) => {
setTimeout(()=>result(2), 500); // don't execute result(2) immediately
});
promise.then(result => {
console.log(result); // Changed r2 to result.
return 2 * result;
}).then(result => console.log(result));
Note the 500ms delay in the output, which is what should be expected from the (corrected) setTimeout code.
I posted a full Promises/A+ compliant promise implementation with comments in this answer
How about very simple:
const SimplePromise = function(cb) {
cb(
data =>
(this.data = data) &&
(this.thenCb || []).forEach(chain => (this.data = chain(this.data))),
error =>
(this.error = error) &&
(this.catchCb || []).forEach(chain => (this.error = chain(this.error)))
);
this.then = thenCb =>
(this.thenCb = [...(this.thenCb || []), thenCb]) && this;
this.catch = catchCb =>
(this.catchCb = [...(this.catchCb || []), catchCb]) && this;
};
Example Here: https://codesandbox.io/s/0q1qr8mpxn
Implying that this r2 is actually the result parameter.
The problem with your code is that you do not retrieve the result from the result(2). The first "then" gets executed, prints 2, return 4, but this 4 is just wasted.
I wrote some code with synchronous function only to demonstrate what to do if you want to get this 2,4 output:
class MyPromise {
constructor(resolver) {
this.resolver = resolver;
}
then(callback) {
var res = callback(this.resolver());
var result = new MyPromise(() => { return res; });
return result;
}
}
let promise = new MyPromise(
() => {
return 2;
});
promise
.then(result => {
console.log(result);
return 2 * result;
})
.then(result => {
console.log(result);
});
If you want the resolver to be somewhat async you should use Promises (because return value from function executed inside setTimeout can be retrieved, see
here.
If you are not allowed to use these built-in Promises, you can write them yourself with some dummy deferred object and await them (setInterval) to resolve (should be basically the same logic).
There are a few problems with your original code. Notably you are only executing the constructor argument upon call of the then method and you aren't actually chaining the outputs of the 'then' callbacks.
Here is a very (very!) basic promise implementation based upon adapting your example. It will also work for cases where 'then' is called after the promise is resolved (but not if 'then' is already called - multiple then blocks is not supported).
class MyPromise {
constructor(resolver) {
let thisPromise = this;
let resolveFn = function(value){
thisPromise.value = value;
thisPromise.resolved = true;
if(typeof thisPromise.thenResolve === "function"){
thisPromise.thenResolve();
}
}
if (typeof resolver === "function") {
resolver(resolveFn);
}
}
then(callback) {
let thisPromise = this;
thisPromise.thenFn = callback;
return new MyPromise((resolve) =>{
thisPromise.thenResolve = () => {
thisPromise.value = thisPromise.thenFn(thisPromise.value);
resolve(thisPromise.value);
}
//automatically resolve our intermediary promise if
//the parent promise is already resolved
if(thisPromise.resolved){
thisPromise.thenResolve();
}
});
}
};
//test code
console.log("Waiting for Godot...");
promise = new MyPromise((resolve) =>{
setTimeout(()=>{
resolve(2)
},500);
});
promise.then((result) => {
console.log(result);
return 2 * result;
}).then((result) => {
console.log(result);
return 2 * result;
}).then((result) => {
console.log(result)
});

Array from recursive promises returns undefined

I am using Jake Archibald's indexedDB promise wrapper.
I have an object store that contains json objects and a separate auto incremented key. When I retrieve the objects i need to also get the key so i can later delete them.
I'm using iterateCursor to recursively step through the cursor so that i can directly add the key and value to an array which i return as a resolved promise.
static getThings(){
return thingsDb.then(db => {
let keyVals = [];
const tx = db.transaction('things');
const unsyncedStore = tx.objectStore('things');
return unsyncedStore.iterateCursor(cursor => {
if(!cursor) return Promise.resolve(keyVals);
console.log(`key: ${cursor.primaryKey}, val: ${cursor.value}`);
keyVals.push({key: cursor.primaryKey, value: cursor.value});
cursor.continue();
});
}).catch(error => {
console.error(error);
});
}
however when I call
DBHelper.getThings().then(returnedArray=> {
// do something with returnedArray
})
it throws an error saying the returned array is undefined.
iterateCursor doesn't return anything (i.e. returns undefined)
You need to return the promise available at unsyncedStore.complete
But this promise won't resolve to a useful value, so, use .then to return the keyVals
Also, no point in doing if(!cursor) return Promise.resolve(keyVals); since the return value from the iterateCursor callback is ignored
static getThings() {
return thingsDb.then(db => {
let keyVals = [];
const tx = db.transaction('things');
const unsyncedStore = tx.objectStore('things');
// no return here since iterateCursor doesn't return anything useful anyway
unsyncedStore.iterateCursor(cursor => {
if (!cursor) return;
console.log(`key: ${cursor.primaryKey}, val: ${cursor.value}`);
keyVals.push({key: cursor.primaryKey, value: cursor.value});
cursor.continue();
});
// return here - complete is a promise that resolves when the iterateCursor completes
return unsyncedStore.complete.then(() => keyVals);
}).catch(error => {
console.error(error);
});
}

Problems with optional promise that has nested promises

I have a server GET request where I am trying to create a promise from a function but it's not going too well. I need this promise to be optional, if that's possible, so that if the result is empty it should be ignored (I've marked where it shouldn't carry on with the function with the words IGNORE FUNCTION).
The idea is that it loops through the docs inside the snapshot, and adds them to an array calls jamDocs. Then if that array isn't empty it should loop through each jam and call an async function for each of them. This then sets the jam's hostName. When this is completed, the original promise should return the jamDocs.
If the jamDocs are empty, the whole promise should be ignored. Finally, after other promises are completed (there is one called profilePromise that returns a profile object), the jams should be assigned to the profile object, and the object sent back to the client with a 200 status code.
My code:
var jamsQuery = firebase.db.collection('users')
.where("hostId", "==", id)
.orderBy("createdAt")
.limit(3)
var jamsPromise = jamsQuery.get().then(snapshot => {
var jamDocs = []
snapshot.forEach(doc => {
var jam = doc.data()
jam.id = doc.id
jamDocs.push(jam)
})
if (!snapshot.length) { // Error: Each then() should return a value or throw
return // IGNORE FUNCTION
} else {
var jamNamePromises = jamDocs.map(function(jam) {
var jamRef = firebase.db.collection('users').doc(jam.hostId)
return jamRef.get().then(doc => {
if (doc.exists) {
jam.hostName = doc.data().firstName
return jam
} else {
throw new Error("yeah")
}
})
})
Promise.all(jamNamePromises)
.then(function() {
return jamDocs
})
.catch(...)
}
})
// Later on
Promise.all(profilePromise, jamsPromise)
.then(objectArray => {
var profile = objectArray[0]
myProfile.jams = jamDocs
return res.status(200).send(myProfile);
})
.catch(
res.status(500).send("Could not retrieve profile.")
)
I get errors like: Each then() should return a value or throw and Avoid nesting promises. How do I fix this? Thanks!
Your then on this line doesn't return anything and also has nested promises in it.
var jamsPromise = jamsQuery.get().then(snapshot => {
You should refactor it, to move the nested promises outside of this then:
var jamsPromise = jamsQuery.get()
.then(snapshot => {
var jamDocs = []
snapshot.forEach(doc => {
var jam = doc.data()
jam.id = doc.id
jamDocs.push(jam)
})
if (!snapshot.length) {
return // IGNORE FUNCTION
} else {
var jamNamePromises = getJamDocsPromises();
return Promise.all(jamNamePromises);
})
.catch(...)
});
function getJamDocsPromises(jamDocs) {
return jamDocs.map(function(jam) {
var jamRef = firebase.db.collection('users').doc(jam.hostId)
return jamRef.get().then(doc => {
if (doc.exists) {
jam.hostName = doc.data().firstName
return jam
} else {
throw new Error("yeah")
}
});
}
If the result is empty it should be ignored (shouldn't carry on with the function). If that array isn't empty it should loop through each jam
Don't make it more complicated than it needs to be. Looping over an empty collection does nothing anyway, so you don't need a special case for that. Just continue normally with the empty array.
var jamsPromise = jamsQuery.get().then(snapshot => {
var jamDocs = []
snapshot.forEach(doc => {
jamDocs.push(Object.assign(doc.data(), {id: doc.id}))
})
return Promise.all(jamDocs.map(function(jam) {
return firebase.db.collection('users').doc(jam.hostId).get().then(doc => {
if (doc.exists) {
jam.hostName = doc.data().firstName
return jam
} else {
throw new Error("yeah")
}
})
}))
})
I get errors like: Each then() should return a value or throw
You had forgotten a return in front of the Promise.all.
and Avoid nesting promises. How do I fix this?
There's nothing wrong with nesting promises, but in your case the .then(() => jamDocs) was not necessary since the Promise.all already resolves with an array of all the jams that the inner promises fulfill with.

Promise.then is not a function - handling multiple API calls in React

I'm using react-select to auto-complete options in a search bar. The search bar displays the results in one of two categories, depending on which API endpoint it hits.
Right now, it works with data from either one point or the other, but I'm having trouble returning data from both endpoints to react-select's loadOptions parameter.
From this answer about multiple API calls, I decided to use promises to return all the data at once, but I get the error Uncaught TypeError: promise.then is not a function at Async.loadOptions
Here's my code for loadOptions:
const getAsync = (tripId, destinationIndex, input) => {
if (!input) {
return { options: [] }
}
function getMusement(input) {
return new Promise(function(resolve, reject) {
TVApi.musement.autocomplete(input)
.then((m) => {
const musementOptions = m.map(musementToOption).slice(0, 4)
return resolve(musementOptions)
})
})
}
function getFourSquare(tripId, destinationIndex, input) {
return new Promise(function(resolve, reject) {
TVApi.spot.autocomplete(tripId, destinationIndex, input)
.then((fs) => {
const fsOptions = fs.map(spotToOption).slice(0, 4)
return resolve(fsOptions)
})
})
}
return Promise.all([getMusement(input), getFourSquare(tripId, destinationIndex, input)])
.then((allData) => {
const merged = [].concat.apply([], allData)
console.log(JSON.stringify(merged)) // logs out with correct data
return {options: merged}
})
}
Your problem is that getAsync does not always return a promise, so you could not chain .then(…) to every call. When there is no input, you were returning a plain object - instead you need to return a promise that is resolved with that object:
if (!input) {
return Promise.resolve({ options: [] });
}
So it turns out the if statement was causing the error:
if (!input) {
return {options: []}
}
but I have no idea why that would. If someone could explain why, that would be good to know for future issues.
Here's the solution I got following #Bergi's advice avoiding the Promise Constructor antipattern
const loadOptions = (tripId, destinationIndex, input) => {
function getMusement(input) {
return TVApi.musement.autocomplete(input)
.then((m) => {
const musementOptions = m.map(musementToOption).slice(0, 3)
return musementOptions
})
}
function getFourSquare(tripId, destinationIndex, input) {
return TVApi.spot.autocomplete(tripId, destinationIndex, input)
.then((fs) => {
const fsOptions = fs.map(fsToOption).slice(0, 2)
return fsOptions
})
}
return Promise.all([getMusement(input), getFourSquare(tripId, destinationIndex, input)])
.then((allData) => {
const merged = [].concat.apply([], allData)
return {options: merged}
})
}

Categories

Resources