Chaining/Nesting promises - javascript

I'm having nightmare trying to chain aws js sdk. Currently I have the following (omitted any parameters to remove chaff, the calls work in isolation so its safe to assume they are correct)
function deploy() {
return sts.assumeRole().promise()
.then(() => {
return new Promise((resolve, reject) => {
return new Promise((resolve, reject) => {
AWS.config.update({
credentials: {
accessKeyId: data.Credentials.AccessKeyId,
secretAccessKey: data.Credentials.SecretAccessKey,
sessionToken: data.Credentials.SessionToken
}
});
resolve();
})
.then(() => {
fsPromise.readFile(packageLocation).then(() => {
return s3.upload().promise();
});
});
}).then(() => {
return ebs.createApplicationVersion().promise();
}).then(() => {
return ebs.createEnvironment().promise();
});
};
If I run each then one by one in order it works, however when i run them together the createApplicationVersion call fails as the upload hasn't worked due to the fact it is attempting to upload before assumeRole has finished. I assume...
I am obviously doing something wrong, but It isn't clear to me what.

The use of promises there is much more complicated than necessary. Remember that then (and catch and finally) return new promises that are settled based on what their handlers return. new Promise within a then (or catch or finally) handler is almost never necessary; just return whatever you would resolve that promise with (a value or another promise), and the promise returned by then et. al. will be resolved to it.
That chain should look like this, if I assume AWS.config.update() is synchronous:
function deploy() {
return sts.assumeRole().promise()
.then(() => {
AWS.config.update();
})
.then(() => fsPromise.readFile(packageLocation))
.then(() => s3.upload().promise())
.then(() => ebs.createApplicationVersion().promise())
.then(() => ebs.createEnvironment().promise());
}
Each step in that chain will wait for the previous step to complete.
If you don't want deploy to fulfill its promise with the fulfillment value of ebs.createEnvironment().promise(), then add a final then handler:
.then(() => ebs.createEnvironment().promise())
.then(() => { });
}
(I'll assume you want to do that for the following examples.)
Or like this if AWS.config.update() is asynchronous and returns a promise:
function deploy() {
return sts.assumeRole().promise()
.then(() => AWS.config.update())
.then(() => fsPromise.readFile(packageLocation))
.then(() => s3.upload().promise())
.then(() => ebs.createApplicationVersion().promise())
.then(() => ebs.createEnvironment().promise())
.then(() => { });
}
Or, of course, in any relatively recent version of Node.js (I'm guessing from fsPromise and just general context that this is Node.js), you could use an async function:
async function deploy() {
await sts.assumeRole().promise();
await AWS.config.update(); // Or without `await` if it is synchronous and doesn't return a promise
await fsPromise.readFile(packageLocation);
await s3.upload().promise();
await ebs.createApplicationVersion().promise();
await ebs.createEnvironment().promise();
}
Side note about this code, and code like it you posted in a comment:
return new Promise((resolve, reject) => {
AWS.config.update({
credentials: {
accessKeyId: data.Credentials.AccessKeyId,
secretAccessKey: data.Credentials.SecretAccessKey,
sessionToken: data.Credentials.SessionToken
}
});
resolve();
})
There's no reason to use new Promise there. It doesn't convert the call to AWS.config.update into an asynchronous call or anything like that. If for some reason you needed a promise there (you don't in this case, AKAICT), you'd use Promise.resolve:
AWS.config.update(/*...*/);
return Promise.resolve();
But again, no need for that here. Remember that promises only provide a way to observe the result of an asynchronous process, they don't make a process asynchronous. (The only thing that they actually make asynchronous is observing the result.)

You don't need to nest your promises the way that you did. Taking a stab in the dark, try this:
function deploy() {
return sts.assumeRole().promise()
.then(() => { AWS.config.update() })
.then(() => fsPromise.readFile(packageLocation))
.then(() => s3.upload().promise())
.then(() => ebs.createApplicationVersion().promise())
.then(() => ebs.createEnvironment().promise())
};

Related

Promises jumping too fast to then() (vanilla JS)

I have a problem with promises. The point is that the one function isn't completed and the executing code is jumping to then method.
const load = (function() {
const site = function(url, parent) {
return fetch(url)
.then(response => {
return response.text()
})
.then(data => {
parent.innerHTML = data;
})
};
return {
site: site,
};
})();
function mainApp() {
firebase.auth().onAuthStateChanged((user) => {
if (user) {
console.log(document.querySelector('.content'));
const welcome = document.createElement('div');
const content = document.querySelector('.content');
welcome.textContent = `Welcome, ${user.displayName}`;
content.appendChild(welcome);
} else {
}
});
}
function promise() {
return new Promise((resolve, rejected) => {
load.site('pages/main/main.html', content);
//this function isn't completed and executing code is jumping to then
})
}
promise().then(() => {
console.log('then');
mainApp(); //this function is called before the upper code is completed
});
And because of this bug I can't change the DOM in mainApp function because this function is completed before the load function (which is also changing to DOM by using fetch). My goal is to firstly call and complete load.site() and then mainApp() but I don't know what's happening now and where is the problem.
I assume that you assumed that we assume that you resolved the function like
function promise() {
return new Promise((resolve, rejected) => {
load.site('pages/main/main.html', content);
resolve(); // Like this.
})
}
The Problem
When you call promise. It calls load.site and then resolves. So why isn't it giving the expected behavior.
Well, the problem is that load.site is a synchronous function, so it does not wait for the fetch call and returns which resolves promise before fetch is resolved. So, the code in promise.then() runs before the code in fetch.then()
The Solution
Make load.site an asynchronous function so that it returns a promise that can be awaited. i.e.
const site = async function (url) {
return fetch('a.js')
.then(response =>{
return response.text()
})
.then(data =>{
console.log("Loaded");
})
};
Then make the promise's executor async so that you await load.site. i.e.
function promise() {
return new Promise(async (resolve, rejected) => {
await load.site('pages/main/main.html'); // This line does the magic
resolve();
});
};
This will make sure that your promise resolves after fetch has resolved. So that you can get the expected behavior.
Hope it helps. Keep on coding

Why use a promise when making an axios api call?

I was following a vue.js tutorial and saw something that confuses me and was wondering if someone can explain it to me since I never use promise. The below method is used to assign a customer array object. Why use a Promise? I thought Promise should be used when you are returning an object to a service consumer? Why and when should I use a promise?
loadCustomer() {
new Promise((resolve, reject) => {
axios.get(this.DetailsDataUrl)
.then(res => {
this.Customer = res.data
resolve()
})
.catch(err => {
console.log(err);
reject()
})
});
}
With promises you can call asynchronous functions.
e.g. here when you want to use loadCustomer you can await until this function resolve or reject:
try {
// resolve
const response = await loadCustomer()
} catch(err) {
// reject
console.log(err)
}
axios it self return a promise:
so you can rewrite your function like this:
loadCustoemr() {
return axios.get(this.DetailsDataUrl)
}
and call it:
loadCutomer()
.then(res => this.Customer = res.data)
.catch(err => console.log(err))
as above you can also use async/await here.
for more information you can use this link,

Nesting .then() functions

Is it bad practice to nest multiple then functions? It seems fairly logical to say "execute this function, and when it's done, execute this one" (and so on) but the code looks horrible.
If it helps I originally had this query in the context of firestore getting user details then getting documents
firebaseApp.auth().signInWithEmailAndPassword(email, password).catch(function(error) {
//If error
}).then(()=>{
firebaseApp.firestore().collection(collectionName).where("associatedID", "==", authID).get().then((snapshot)=>{
snapshot.docs.forEach(doc => {
//Do stuff with data that we've just grabbed
})
}).then(()=>{
//Tell the user in the UI
});
});
Are there alternatives? One that springs to mind is like so
var functionOne = () =>{
console.log("I get called later");
}
var promise1 = new Promise(function(resolve, reject) {
setTimeout(function() {
resolve('foo');
}, 3000);
});
promise1.then(function(value) {
functionOne();
});
But even then it seems like it could get complex after a few .then()'s
Return the Promise from the first outer .then, and then use the resolve value in a second outer .then, without any nested .thens:
firebaseApp.auth().signInWithEmailAndPassword(email, password)
.then(()=>{
return firebaseApp.firestore().collection(collectionName).where("associatedID", "==", authID).get()
})
.then((snapshot) => {
snapshot.docs.forEach(doc => {
//Do stuff with data that we've just grabbed
});
//Tell the user in the UI
})
.catch((error) => {
// handle errors
});
Make sure not to catch too early - if there's an error anywhere in the chain, often you'll want to stop normal execution and go directly to the end (eg, tell the user that there was an error).
If you're worried about code readability, consider using async/await (and transpile down your production code for older browsers):
// in an async function:
try {
await firebaseApp.auth().signInWithEmailAndPassword(email, password);
const snapshot = await firebaseApp.firestore().collection(collectionName).where("associatedID", "==", authID).get()
snapshot.docs.forEach(doc => {
//Do stuff with data that we've just grabbed
});
//Tell the user in the UI
} catch(error) {
// handle errors
}
It depends on what you want to do: If you need access both to the result passed into then and to the result of a subsequent operation you're doing within the then at the same time, nesting is reasonable:
doSomething()
.then(result1 => {
return doSomethingElse()
.then(result2 => {
return result1 + result2;
});
})
.then(combinedResult => {
// Use `combinedResult`...
})
.catch(/*...*/);
often, though, you just need to pass a single value through the chain, by returning the promise from your subsequent operation from the then handler:
doSomething()
.then(result => {
return doSomethingElse(result);
})
.then(lastResult => {
// `lastResult` is the fulfillment value from `doSomethingElse(result)`
})
.catch(/*...*/);
Doing that resolves the promise then created to the promise returned by get() on the query. (To "resolve a promise to something" means that you've made the promise's settlement depend on the thing you've resolved it to. If you resolve it to another promise, its settlement depends on the settlement of that other promise.)
Looking at your Firebase example, I'd probably do it without nesting:
firebaseApp.auth()
.signInWithEmailAndPassword(email, password)
.then(() => firebaseApp.firestore().collection(collectionName).where("associatedID", "==", authID).get())
.then((snapshot) => {
snapshot.docs.forEach(doc => {
// Do stuff with data
});
})
.then(() => {
// Tell the user in the UI
})
.catch(function(error) {
// Handle/report error, which may be from `signInWithEmailAndPassword`, your collection query, or an error raised by your code in the `then` handlers above
});
You should chain promises and, also, you can name the functions, which IMHO can improve readibility significantly. Consider something like this
const signIn = () => firebaseApp.auth().signInWithEmailAndPassword(email, password);
const onSigninError = (err) => // error handling logic here
const getCollection = () => firebaseApp.firestore().collection(collectionName).where("associatedID", "==", authID)
.get();
const processSnapshot = (snapshot) => snapshot.doc.forEach(// do stuff here
const displayMessage = () => // do stuff here
signIn()
.catch(onSigninError)
.then(getCollection)
.then(processSnapshot)
.then(displayMessage);

Javascript Promise resolve confusion

private runMiddlewares(route: Route | ExceptionRoute, request: HttpRequest, response: HttpResponse): Promise<any> {
return new Promise((resolve, reject) => {
try {
route.middlewares.forEach(async (middleware: IMiddleware) => {
console.log('begin run middleware');
await middleware.process(request, response);
console.log('resolve run middleware');
console.log(request.body);
});
console.log('resolve all runMiddlewares');
resolve();
} catch (e) {
reject(e);
}
});
}
I have written this function runMiddlewares which should ideally resolve() when all the middleware.process() have resolved. I am using typescript await functionality but it doesn't seem to be working.
I expect something like this to happen inside route.middlewares.forEach(
'begin run middleware'
then awaits resolves
'resolve run middleware'
This continues for all of the middlewares in forEach loop and when the list is all done then and only then 'resolve all runMiddlewares' should be printed and finally, private runMiddlewares( ... ) should resolve.
But instead, forEach is now getting immediately resolved thus preventing all of the middlewares to even complete.
How should this be handled? I thought that await will take care of it inside the forEach loop and only then resolve of runMiddlewares will be called in the end.
What am I missing here?
You can use map to create an array of promises from your middlewares. This array of promises can ben handed to Promise.all which resolves when every single promise of the array has been resolved.
await Promise.all(route.middlewares.map((middleware: IMiddleware) => {
console.log('begin run middleware');
const promise = middleware.process(request, response);
console.log('resolve run middleware');
console.log(request.body);
return promise
});
Or even more compact:
runMiddlewares(...) {
return Promise.all(
route.middlewares.map((middleware: IMiddleware) => {
return middleware.process(request, response))
})
)
}
So, following the recommendation ( https://cn.eslint.org/docs/3.0.0/rules/no-await-in-loop ), I wrote it as follows
private runMiddlewares(route: Route | ExceptionRoute, request: HttpRequest, response: HttpResponse): Promise<any> {
return new Promise(async (resolve, reject) => {
try {
const middlewarePromiseArry: any[] = [];
route.middlewares.forEach((middleware: IMiddleware) => {
console.log('begin run middleware');
middlewarePromiseArry.push(middleware.process(request, response));
// removed the use of await inside of forEach loop following this link: https://cn.eslint.org/docs/3.0.0/rules/no-await-in-loop
// await middleware.process(request, response);
// console.log('resolve run middleware');
// console.log(request.body);
});
await Promise.all(middlewarePromiseArry);
console.log('resolve all runMiddlewares');
resolve();
} catch (e) {
reject(e);
}
});
}
I am happy to accept further answers and recommendations/improvements :)

Promise: chained timeout

The Promise API doesn't have a chainable timeout option, but in Steve Sanderson's presentation at NDC Conference (at 15:31 here https://www.youtube.com/watch?v=9G8HEDI3K6s&feature=youtu.be&t=15m31s), he presented an elegant chainable timeout on the fetch API. It looks like this:
The great thing about this approach is that the resolve handler still completed (e.g. the response was still put into cache) even after the timeout. He demo'd this during his presentation (and available at the YouTube link above). Anyone know how this chainable timeout was implemented?
I usually use Promise.race to implement timeouts. Normally I haven't tried to make this chainable, but that's a cool idea so I'll give it a go in a moment.
This is how I'd normally use it to implement a timeout:
function timeout (promise, duration) {
return Promise.race([
promise,
new Promise((resolve, reject) => {
setTimeout(
() => reject(new Error("Timeout")),
duration
)
})
]);
}
timeout(fetch("something"), 5000)
.then(() => {
// ... Operation completed without timeout ...
})
.catch(err => {
// ... An error occurred possibly a timeout ...
));
You might be able to make this chainable by attaching your function to the prototype of the Promise class (maybe depending on which promise library you are actually using):
Promise.prototype.timeout = function (duration) {
return Promise.race([
this,
new Promise((resolve, reject) => {
setTimeout(
() => reject(new Error("Timeout")),
duration
)
})
]);
};
fetch("something")
.then(() => ...) // This executes before the timeout.
.timeout(5000)
.catch(err => ...);

Categories

Resources