Complex method chaining [duplicate] - javascript

I was going through some tutorial materials and I notice that in Mongoose, I could somehow defer the final execution of a promise in it but I wonder how that is done.
For example, I can call the find function and it returns the promise of the result likes so:
const blogs = await Blog.find({id : '123'});
The find() function in mongoose calls the exec() function in the Query object to complete the query and return the result, like in the line of this file.
Then, let's say that I have the following modifications to the prototype of the Mongoose Query object to see whether I should retrieve data from cache or from Mongo:
mongoose.Query.prototype.cache = function() {
this.useCache = true;
return this;
}
mongoose.Query.prototype.exec = async function() {
if (!this.useCache) { // <-- Apparently, I don't understand how this.useCache can be true if this.cache() was called
this.exec.apply(this, arguments);
}
return 'some cached value';
return this;
}
// Somehow, the find() section of the chaining is capable of waiting for cache() which is called later to complete to know that useCache is true! But how is that done?
const blogs = await Blog.find({id : '123'}).cache(); // <-- how did this mange to return 'some cached value'?
However, since exec() is already called and evaluated in find() which is executed before the cache() function in the chain, how could this.useCache still be eventually evaluated in the exec() function finally when it resolves?
Unless there are some ways to wait until everything else down the chain have finished executing, in this case find() waited for cache() to finish executing, I would have expected this.useCache to always be undefined, wouldn't it?
I thought it's pretty amazing and would actually like to know how to implement a similar kind of chaining that is capable of somehow deferring the final action until all the functions in the later part of the chain to complete before resolving a result.
Note:
The above examples are my simplified version of the actual code for readability sake. The links to their actual files can be seen here and here.

The find() function in mongoose calls the exec() function in the Query object to complete the query and return the result, like in the line of this file.
Actually no, it doesn't, at least not in your case. See the comment from three lines above:
// if we don't have a callback, then just return the query object
The .exec() method is only called by find(…, callback) if you pass a callback. When using promises, you don't.
Instead, the exec method is called by the then() method of the query which makes the query thenable, and which gets used when you await the query object.

Related

Does the result of an api call in JS have to be awaited?

I have the following endpoint in a class called UserApi.js:
const controller = 'User';
...
export async function getEmployeeInfo(employeeId)
{
const query = createQueryFromObject({employeId});
const response = await get(`/${controller}/EmployeeInfo?${query}`);
return retrieveResponseData(response, []);
}
This is going to get the required information from an action method in the backend of UserController.cs.
Now, say that I want to display this information in EmployeeView.vue class, do I have to await it again? Why or why not? Initially, I would say no, you don't, as you already dealt with the await/async in the UserApi.js class, but what about the Promise.resolve? Please explain.
methods: {
async setReportData(
employeeId
) {
this.isBusy = true;
Promise.resolve(getEmployeeInfo(
employeeId
)).then((resultsEmployeeInfo) => {
this.reportDatatableEmployeeInfo = resultsEmployeeInfo;
})
.catch(() => {
this.alerts.error('An error has occurred while fetching the data');
})
.finally(() => {
this.isBusy = false;
});
},
Update:
....
* #param {Object} response
* #param {any} defaultData
* #param {Function} predicate
* #returns {Promise}
*/
export function retrieveResponseData(response, defaultData = null, predicate = (predicateResponse) => predicateResponse) {
const data = predicate(response) ? response.data : null;
return data || defaultData;
}
You need to await it since a function declared with async keyword ALWAYS returns a Promise, even if you do only synchronous stuff inside of it, hence you need to await or "thenize" it to access the value it resolved to. That depends from the implementation details of async functions which are just generators that yield promises.
If this concerns you because you work inside sync modules and don't like to use async functions just to execute more async functions, there's a good news, TOP-LEVEL await MODULES proposal is at stage 4, so it'll very soon be shipped with the next ECMA version. This way you will be able to await inside modules as if they were wrapped by async functions !
https://github.com/tc39/proposal-top-level-await
I can't tell if you need to await it again, because I can't tell what retrieveResponseData does. It might take the resolved value and wrap it in a fresh promise, which would then require callers of getEmployeeInfo to await the result.
Here's the why:
A Promise is a wrapper around a value
await unwraps a Promise. So does the .then() handler you can register with a Promise (but the value is only unwrapped within the function you provide to .then()).
Just like a gift in the real world, once something has been unwrapped, you don't need to unwrap it again. However, very conveniently for us, you can still use await on a value that is not wrapped in a Promise, and it will just give you the value.
You can wrap any value in a Promise, like so:
let wrappedFive = Promise.resolve(5)
//> wrappedFive is a Promise that must be unwrapped to access the 5 inside it
// this does _exactly_ the same thing as the above
let wrappedFive = new Promise(resolve => {
resolve(5)
})
Sometimes you end up in a situation where you can't use await, because you're in a function that cannot be marked async. The lifecycle methods of front-end frameworks like React (and possibly Vue) are like that: the framework needs each lifecycle method to do its job and be done immediately. If you mark the lifecycle method as async, you can often prevent it from having the intended effect.
In cases like that, you often need to use chained .then() handlers, which is a little uglier, but it works:
componentDidMount() {
// this API call is triggered immediately by lifecycle,
// but it basically starts a separate thread -- the rest
// of this function does not wait for the call to finish
API.getUserInfo()
.then(userInfo => {
// this happens after the API call finishes, but
// componentDidMount has already finished, so what happens
// in here cannot affect that function
this.setState({ username: userInfo.username })
})
// this happens immediately after the API call is triggered,
// even if the call takes 30 seconds
return 5
}
Note that a Promise does not actually start a separate thread -- these all happen in the same thread that executes the lifecycle method, i.e. the browser's renderer thread. But if you think of the codepath that executes, a Promise that you don't wait for basically introduces a fork into that codepath: one path is followed immediately, and the other path is followed whenever the Promise resolves. Since browserland is pretty much a single-threaded context, it doesn't really hurt you to think of creating a Promise as spawning a separate thread. This is a nuance you can ignore until you are comfortable with asychronous patterns in JS.
Update: retrieveResponseData does not appear to return a Promise. I could be wrong, if predict returns a Promise, but if that were true, then the ternary would always return response.data because unwrapped Promises are truthy values (even Promise.resolve(false) is truthy).
However, anyone who calls getEmployeeInfo will have to wait it, because that function is marked async, and that means it returns a Promise even if nothing inside it is is asynchronous. Consider this extreme example:
// this function returns a number
function gimmeFive() {
return 5
}
// this function returns a Promise wrapped around a number
async function gimmeFive() {
return 5
}
Async function getEmployeeInfo awaits the result of the get call in order to return the value returned by a call to retrieveResponeData.
Assuming neither get nor retrieveResponeData errors, the value syntactically returned in the body of getEmployeeInfo is used to resolve the promise object actually returned by calling getEmployeeInfo.
Promise.resolve is not needed to convert the result of calling getEmployeeInfo into a promise because, given async functions return promises, it already is.
It doesn't matter if retrieveResponseData returns a promise or not: standard async function processing waits for a returned promise value to be settled before resolving the promise returned when calling the async function.
Async function setRreportData is declared as async but written using chained promise handler methods to process data and error conditions in order - the async declaration could be omitted, but may be useful if modifications are made.
The results can only be used to update the page at a time when the data has finished being obtained and extracted, shown as a comment in the following code:
setReportData( employeeId) {
this.isBusy = true;
getEmployeeInfo(
employeeId
).then((resultsEmployeeInfo) => {
this.reportDatatableEmployeeInfo = resultsEmployeeInfo;
// At this point the result in this.reportDatatableEmployeeInfo can be used to update the page
})
.catch(() => {
this.alerts.error('An error has occurred while fetching the data');
})
.finally(() => {
this.isBusy = false;
});
},
Displaying the data using EmployeeView.vue class must wait until the data is available. The simplest place to insert updating the page (in the posted code) is within the then handler function inside setReportData.
Displaying the data
As posted setReportData does not notify its caller of when report data is available, either by means of a callback or by returning a promise. All it does is save the result in this.reportDatatableEmployeeInfo and dies.
Without using callbacks, setReportData is left with two choices
Return a promise that is fulfilled with, say,resultsEmployeeInfo or undefined if an error occurs:
setReportData( employeeId) {
this.isBusy = true;
// return the final promise:
return getEmployeeInfo(
employeeId
)
.then( (resultsEmployeeInfo) =>
(this.reportDatatableEmployeeInfo = resultsEmployeeInfo)
)
.catch(() => {
this.alerts.error('An error has occurred while fetching the data');
// return undefined
})
.finally(() => {
this.isBusy = false;
});
},
which could be used in a calling sequence using promises similar to
if(!this.busy) {
this.setReportData(someId).then( data => {
if( data) {
// update page
}
}
If you wanted to make the call in an async context you could use await:
if(!this.busy) {
const data = await this.setReportData(someId);
if( data) {
// update page
}
}
Update the page from within setReportData after the data becomes available ( as shown as a comment in the first part of this answer). The method should probably be renamed from setReportData to getReportData or similar to reflect its purpose.

Should I have been returning a promise all along?

I have been calling a method MediaNext() from several points in my PlayersController code to chain the next Media playback if any.
This MediaNext() method returns a Media object from some of the various playlists I have from memory. So it tries to encapsulate the notion of getting the next Media object if any left.
However, recently I revised MediaNext() that for some new condition X I now have to load external data in order to return this alternative Media object.
MediaNext():any { // any could be typed like Media_I
if(cond1) {
...
return Media1
}
if(cond2) {
...
return Media1
}
//
if(! AutoPlayNext) return null;
//
if(NoMoreMedia) return null;
//
If (condX) {
// in order to wait for the Load() method I am subscribing to its promise
this.Service1.Load(Media).then(() => {
...processing...
return Media
})
??? what do I return here, it does not make sense to return null, since potentially a Media is on its way ???
}
return null // safe catch. nomore media should not happen.
}
The questions I have
By subscribing to the promise of the .Load(), the execution from what I gather in JS should not be blocked waiting for the media data to return. Instead the execution needs to flow and the call to MediaNext() has to return. However, what could I be returning where indicated in the code above "???".
Should MediaNext() have been coded to return a promise for all conditions?
I mostly do guess work when I code promises and observables as they remain a beast to understand and master so feel free to advise alternatives.
Thank you for your help.
If you deal with .Load asynchronously and immediately return, there's no good reason for that function to return anything. The operation is still happening in the backend.
However, if you want the caller to get more information about if the operation was successful or not, you should return a promise.
If you want to return a promise, you might find it much easier to write this as an async function, and use await this.Service1.Load(). instead of then().
You method is now asynchronous and you are using a Promise. You then need to return a promise which your consumers can wait on to get a result. Either:
1) Return the promise
If (condX) {
return this.Service1.Load(Media);
}
2) Set your method as async and await the promise used internally
async MediaNext():any { // any could be typed like Media_I
// ...
If (condX) {
return await this.Service1.Load(Media);
}
// ...

Correct way to return a promise from firebase's onCall function?

According to the docs I've read, an onCall function should return a promise. Something like this:
...onCall((data, context) => {
return db.collection('game').doc(game_id).delete();
});
...
This simply takes the promise associated with the delete function and returns it to the caller.
However, if I want to take an additional step after delete, then there are multiple return paths and I'm not clear on what to return:
...onCall((data, context) => {
return db.collection('game').doc(game_id).delete()
.then(){
return db.collection('user').doc(user_id).delete()
}
//.catch(){ return ??? }
});
...
I've seen code similar to what I've typed above, but it isn't clear to me that this is correct.
First, is it saying that by returning the game deletion promise, if there is an error, it will be returned to the user and if it is successful, then the user deletion promise will be returned to the user? What if there is a user deletion exception?
Do I have to create my own Promise object and call resolve or reject myself?
It's fine the way it is. You almost never need to create a new Promise. That's for cases where you call a function that doesn't return a promise, and provides a callback instead.
There are a lot of resources out there on learning to deal with promises. It doesn't really matter if the code is running in Cloud Functions or not - JavaScript promises work the same in any context. A callable function just needs that promise to know when the work is complete, and what to return to the caller.
...onCall((data, context) => {
return db.collection('game').doc(game_id).delete()
.then(){
return db.collection('user').doc(user_id).delete()
}
//.catch(){ return ??? } });
I've seen code similar to what I've typed above, but it isn't clear to
me that this is correct. First, is it saying that by returning the
game deletion promise, if there is an error, it will be returned to
the user and if it is successful, then the user deletion promise will
be returned to the user? What if there is a user deletion exception?
What you have in this code is Promise chaining, with the then() method. Since a call to promise.then() returns a Promise, the first return does return a promise and therefore it is fine for the Callable Cloud Function.
However, to answer to the end of your question, you should note that:
If there is an error in the game deletion, the CF will stop and neither the game nor the user will be deleted
If there is an error in the user deletion, the CF will stop and the game will be deleted (successfully deleted before the error) but not the user.
If you want to be sure both are deleted in one atomic operation (both are deleted or none is deleted), you should use a batched write as follows:
...onCall((data, context) => {
let batch = db.batch();
const gRef = db.collection('game').doc(game_id);
batch.delete(gRef);
const uRef = db.collection('user').doc(user_id);
batch.delete(uRef);
return batch.commit()
});

Chaining functions that return promises in js

The idea is that I need to run multiple operations against the database, but only if I need to. For instance if there are no items to insert, then there is no insert call.
function dbOperations(params){
functionInsert(params)
.then(functionUpdate(params))
.then(functionDelete(params))
...
}
then I have
function functionInsert(params){
if (params){
//DO a call against DB which returns a promise
return knex.insert().transacting(trx);
}else{
//if no params, no need to do anything, just let the next .then() fire next function
return Promise.resolve()
}
}
By having it like that, the code runs fine but when there are no params then I see this warning Warning: .then() only accepts functions but was passed: [object Object] so it's obvious I am doing something wrong.
How should this scenario be handled, when there are no params?
LE: for db access I am using knex. I've edited the functionInsert above.
The warning itself is explaining. .then expects a function as its argument, but you're passing the result of the function functionUpdate instead.
You may want to wrap the statement(s) in anonymous functions instead, as pointed out by #Thilo in the comments:
function dbOperations(params){
functionInsert(params)
.then(() => functionUpdate(params))
.then(() => functionDelete(params))
...
}

JavaScript Chaining Promises : Calling next promise before previous has finished

Tools: JavaScript ES6
I haven't seen a good succinct answer about the syntax of chaining multiple promises to execute in order. I thought this would be a good nail in the coffin question for all promise newbies out there. :)
My Issue is that I want to call this in a synchronous order getPosts--->getThreads--->initializeComplete()
Here is what I am doing.
userPromise.then(getPostsPromise).then(getThreadsPromise).then(initializeComplete());
userPromise is Promise obj I returned from another part of the code
getPostsPromise returns a Promise and makes a fetch to the server for posts
getThreadsPromise returns a Promise and makes a fetch to the server for threads
initializeComplete is a callback to tell my program that it is initialized.
Here is an example of one of the promises in the chain:
var getPostsPromise = function(){
//Firebase is just a simple server I'm using
var firebasePostsRef = new Firebase("https://myfburl.firebaseio.com/posts");
var postsRef = firebasePostsRef.child(localPlace.key);
return new Promise(function(resolve, reject) {
//Below is a Firebase listener that is called when data is returned
postsRef.once('value', function(snap,prevChild) {
var posts = snap.val();
AnotherFile.receiveAllPosts(posts);
resolve(posts);
});
});
}
But initializeComplete() is being called before getPostsPromise and getThreadsPromise have a chance to finish fetching.
Why is that happening and how do I write the promises to execute in order?
initializeComplete is getting called right away because you are invoking it when passing it to then. You have to omit the parentheses, just like you did for getPostsPromise and getThreadsPromise
userPromise.then(getPostsPromise).then(getThreadsPromise).then(initializeComplete);
While yts's answer is correct (the issue is you're invoking initializeComplete instead of passing the function), I'd rather format the calls a bit differently. Having each callback function call the next function is a bit against the design of promises. I'd rather each function return a promise, and then call then on the returned promise:
userPromise
.then(function(){
return getPostsPromise()
}).then(function(){
return getThreadsPromise()
}).then(function(){
return initializeComplete();
});
or to pass the actual returned objects and not have to do any additional intermediate processing:
userPromise
.then(getPostsPromise)
.then(getThreadsPromise)
.then(initializeComplete);

Categories

Resources