I have read here that unhandled errors might cause cold starts. I am implementing a triggered function, as follows:
exports.detectPostLanguage = functions
.region("us-central1")
.runWith({ memory: "2GB", timeoutSeconds: "540" })
.firestore.document("posts/{userId}/userPosts/{postId}")
.onCreate(async (snap, context) => {
// ...
const language = await google.detectLanguage(post.text);
// ... more stuff if no error with the async operation
});
Do I need to catch the google.detectLanguage() method errors to avoid cold starts?
If yes, what should I do (A or B)?
A:
const language = await google.detectLanguage(post.text)
.catch(err => {
functions.logger.error(err);
throw err; // <---- Will still cause the cold start?
});
B:
try {
var language = await google.detectLanguage(post.text)
} catch(err) {
functions.logger.error(err);
return null; // <----
}
UPDATE
Based on Frank solution:
exports.detectPostLanguage = functions
.region("us-central1")
.runWith({ memory: "2GB", timeoutSeconds: "540" })
.firestore.document("posts/{userId}/userPosts/{postId}")
.onCreate(async (snap, context) => {
try {
// ...
const language = await google.detectLanguage(post.text);
// ... more stuff if no error with the async operation
} catch(err) {
return Promise.reject(err);
}
return null; // or return Promise.resolve();
});
If any exception escapes from the Cloud Function body, the runtime assumes that the container is left in an unstable state and won't schedule it to handle events anymore.
To prevent this, ensure that no exception escapes from your code. You can either return no value, or a promise that indicates when any asynchronous calls in your code are done.
Related
There has been other topics on SE, but most of them are dated 5 years ago. What is the current, up-to-date approach to cancel await call in JS? i.e.
async myFunc(){
let response = await oneHourLastingFunction();
myProcessData(response);
}
at specific moment application decides it no longer want to wait that oneHourLastingFunction, but it is stuck in "await". How to cancel that? Any standard ways of cancelation-tokens/abortControllers for promises?
Canceling an asynchronous procedure is still not a trivial task, especially when you need deep cancellation and flow control. There is no native solution at the moment. All you can do natively:
pass AbortController instance to each nested async function you want to make cancellable
subscribe all internal micro-tasks (requests, timers, etc) to the signal
optionally unsubscribe completed micro-tasks from the signal
call abort method of the controller to cancel all subscribed micro-tasks
This is quite verbose and a tricky solution with potential memory leaks.
I can just suggest my own solution to this challenge- c-promise2, which provides cancelable promises and a cancelable alternative for ECMA asynchronous functions - generators.
Here is an basic example (Live Demo):
import { CPromise } from "c-promise2";
// deeply cancelable generator-based asynchronous function
const oneHourLastingFunction = CPromise.promisify(function* () {
// optionally just for logging
this.onCancel(() =>
console.log("oneHourLastingFunction::Cancel signal received")
);
yield CPromise.delay(5000); // this task will be cancelled on external timeout
return "myData";
});
async function nativeAsyncFn() {
await CPromise.delay(5000);
}
async function myFunc() {
let response;
try {
response = await oneHourLastingFunction().timeout(2000);
} catch (err) {
if (!CPromise.isCanceledError(err)) throw err;
console.warn("oneHourLastingFunction::timeout", err.code); // 'E_REASON_TIMEOUT'
}
await nativeAsyncFn(response);
}
const nativePromise = myFunc();
Deeply cancellable solution (all functions are cancellable) (Live Demo):
import { CPromise } from "c-promise2";
// deeply cancelable generator-based asynchronous function
const oneHourLastingFunction = CPromise.promisify(function* () {
yield CPromise.delay(5000);
return "myData";
});
const otherAsyncFn = CPromise.promisify(function* () {
yield CPromise.delay(5000);
});
const myFunc = CPromise.promisify(function* () {
let response;
try {
response = yield oneHourLastingFunction().timeout(2000);
} catch (err) {
if (err.code !== "E_REASON_TIMEOUT") throw err;
console.log("oneHourLastingFunction::timeout");
}
yield otherAsyncFn(response);
});
const cancellablePromise = myFunc().then(
(result) => console.log(`Done: ${result}`),
(err) => console.warn(`Failed: ${err}`)
);
setTimeout(() => {
console.log("send external cancel signal");
cancellablePromise.cancel();
}, 4000);
I have some difficulties with a nested promise (below).
For now I'm using an async function in the catch to trigger authentication Errors.
But is it really a good way and isn't there a better way ?
The function have to send a POST request. If an authentication Error is thrown then login(), else throw the error.
If login() is fulfilled : retry the POST (and then return the results), else throw the error;
function getSomeData() {
return post('mySpecialMethod');
}
function login() {
const params = { /* some params */ }
return post('myLoginMethod', params).then(result => {
/* some processing */
return { success: true };
});
}
const loginError = [1, 101];
function post(method, params, isRetry) {
const options = /* hidden for brevity */;
return request(options)
// login and retry if authentication Error || throw the error
.catch(async ex => {
const err = ex.error.error || ex.error
if (loginError.includes(err.code) && !isRetry) { // prevent infinite loop if it's impossible to login -> don't retry if already retried
await login();
return post(method, params, true)
} else {
throw err;
}
})
// return result if no error
.then(data => {
// data from 'return request(options)' or 'return post(method, params, true)'
return data.result
});
}
Use
getSomeData.then(data => { /* do something with data */});
I'd suggest that for complex logic at least you use the async/await syntax.
Of course .then() etc is perfectly valid, however you will find the nesting of callbacks awkward to deal with.
My rule (like a lot of things in programming) is use context to make your decision. .then() works nicely when you're dealing with a limited number of promises. This starts to get awkward when you're dealing with more complex logic.
Using async / await for more involved logic allows you to structure your code more like synchronous code, so it's more intuitive and readable.
An example of two approaches is shown below (with the same essential goal). The async / await version is the more readable I believe.
Async / await also makes looping over asynchronous tasks easy, you can use a for loop or a for ... of loop with await and the tasks will be performed in sequence.
function asyncOperation(result) {
return new Promise(resolve => setTimeout(resolve, 1000, result));
}
async function testAsyncOperationsAwait() {
const result1 = await asyncOperation("Some result 1");
console.log("testAsyncOperationsAwait: Result1:", result1);
const result2 = await asyncOperation("Some result 2");
console.log("testAsyncOperationsAwait: Result2:", result2);
const result3 = await asyncOperation("Some result 3");
console.log("testAsyncOperationsAwait: Result3:", result3);
}
function testAsyncOperationsThen() {
return asyncOperation("testAsyncOperationsThen: Some result 1").then(result1 => {
console.log("testAsyncOperationsThen: Result1:", result1);
return asyncOperation("testAsyncOperationsThen: Some result 2").then(result2 => {
console.log("testAsyncOperationsThen: Result2:", result2);
return asyncOperation("testAsyncOperationsThen: Some result 3").then(result3 => {
console.log("testAsyncOperationsThen: Result3:", result3);
})
})
})
}
async function test() {
await testAsyncOperationsThen();
await testAsyncOperationsAwait();
}
test();
... But is it really a good way and isn't there a better way ?
No it's not a good idea because it hurts code readability.
You're mixing 2 interchangeable concepts, Promise.then/catch and async/await. Mixing them together creates readability overhead in the form of mental context switching.
Anyone reading your code, including you, would need to continuously switch between thinking in terms of asynchronous flows(.then/.catch) vs synchronous flows (async/await).
Use one or the other, preferably the latter since it's more readable, mostly because of it's synchronous flow.
Although I don't agree with how you're handling logins, here's how I would rewrite your code to use async/await and try...catch for exception handling:
function getSomeData() {
return post('mySpecialMethod')
}
async function login() {
const params = { } // some params
await post('myLoginMethod', params)
return { success: true }
}
const loginError = [1, 101]
async function post(method, params, isRetry) {
const options = {} // some options
try {
const data = await request(options)
return data.result
} catch (ex) {
const err = ex.error.error || ex.error
if (err) throw err
if (loginError.includes(err.code) && !isRetry) {
await login()
return post(method, params, true)
}
throw err
}
}
I obviously cannot/didn't test the above.
Also worth exploring the libraries which provides retry functionalities.
something like https://www.npmjs.com/package/async-retry
Generally this is not a big problem. You can chain/encapsulate async calls like this.
When it comes to logic it depends on your needs. I think the login state of a user should be checked before calling any API methods that require authentication.
Take the following contrived example:
const housekeepingStuff = async function (data) {
const result = await notImportant(data);
result.more = 'yawn';
storeInDatabase(result);
};
const getStuff = async function () {
try {
const data = await getData();
data.extra = 'wow';
housekeepingStuff(data); // <---- don't want to await... but need to for error catching
return Promise.resolve(data);
} catch (err) {
return Promise.reject(err);
}
};
try {
const myData = await doSomeStuff();
res.send(myData);
} catch (err) {
console.log(err);
res.sendStatus(400);
}
I want to return the data from getStuff () ASAP without waiting for housekeepingStuff() but if I don't await that function then I have an uncaught error.
I could call housekeepingStuff() outside the getStuff() function, after getting and sending the data to whoever wants it:
try {
const myData = await doSomeStuff();
res.send(myData);
await housekeepingStuff(data); // <---- am awaiting but who cares because nothing follows
} catch (err) {
console.log(err);
res.sendStatus(400);
}
But that doesn't seem right because I don't want to have to remember to call housekeepingStuff() every time I call doSomeStuff()... it should ideally be handled "internally".
What is the correct approach here?
A promise (or async) function has 2 possible outcomes:
A successful outcome
An error outcome
To get either outcome, you must wait for it. You can't wait for 1 condition and not for the other, because the entire thing needs to execute so you can find out what the outcome was.
Otherwise you're really asking the javascript engine: Please predict for me if the function will fail, and if it does, await it.
The correct approach therefore is to just await it.
However, if you don't care about either successful or failed outcomes of this function, just call the function via another async function that eats all the errors:
async function doSomeStuffAndIgnoreError() {
try {
await doSomeStuff();
} catch (e) {
console.error(e);
}
}
I like the flatness of the new Async/Await feature available in Typescript, etc. However, I'm not sure I like the fact that I have to declare the variable I'm awaiting on the outside of a try...catch block in order to use it later. Like so:
let createdUser
try {
createdUser = await this.User.create(userInfo)
} catch (error) {
console.error(error)
}
console.log(createdUser)
// business
// logic
// goes
// here
Please correct me if I'm wrong, but it seems to be best practice not to place multiple lines of business logic in the try body, so I'm left only with the alternative of declaring createdUser outside the block, assigning it in the block, and then using it after.
What is best practice in this instance?
It seems to be best practice not to place multiple lines of business logic in the try body
Actually I'd say it is. You usually want to catch all exceptions from working with the value:
try {
const createdUser = await this.User.create(userInfo);
console.log(createdUser)
// business logic goes here
} catch (error) {
console.error(error) // from creation or business logic
}
If you want to catch and handle errors only from the promise, you have three choices:
Declare the variable outside, and branch depending on whether there was an exception or not. That can take various forms, like
assign a default value to the variable in the catch block
return early or re-throw an exception from the catch block
set a flag whether the catch block caught an exception, and test for it in an if condition
test for the value of the variable to have been assigned
let createdUser; // or use `var` inside the block
try {
createdUser = await this.User.create(userInfo);
} catch (error) {
console.error(error) // from creation
}
if (createdUser) { // user was successfully created
console.log(createdUser)
// business logic goes here
}
Test the caught exception for its type, and handle or rethrow it based on that.
try {
const createdUser = await this.User.create(userInfo);
// user was successfully created
console.log(createdUser)
// business logic goes here
} catch (error) {
if (error instanceof CreationError) {
console.error(error) // from creation
} else {
throw error;
}
}
Unfortunately, standard JavaScript (still) doesn't have syntax support for conditional exceptions.
If your method doesn't return promises that are rejected with specific enough errors, you can do that yourself by re-throwing something more appropriate in a .catch() handler:
try {
const createdUser = await this.User.create(userInfo).catch(err => {
throw new CreationError(err.message, {code: "USER_CREATE"});
});
…
} …
See also Handling multiple catches in promise chain for the pre-async/await version of this.
Use then with two callbacks instead of try/catch. This really is the least ugly way and my personal recommendation also for its simplicity and correctness, not relying on tagged errors or looks of the result value to distinguish between fulfillment and rejection of the promise:
await this.User.create(userInfo).then(createdUser => {
// user was successfully created
console.log(createdUser)
// business logic goes here
}, error => {
console.error(error) // from creation
});
Of course it comes with the drawback of introducing callback functions, meaning you cannot as easily break/continue loops or do early returns from the outer function.
Another simpler approach is to append .catch to the promise function. ex:
const createdUser = await this.User.create(userInfo).catch( error => {
// handle error
})
Cleaner code
using async/await with Promise catch handler.
From what I see, this has been a long-standing problem that has bugged (both meanings) many programmers and their code. The Promise .catch is really no different from try/catch.
Working harmoniously with await/async, ES6 Promise's catch handler provides a proper solution and make code cleaner:
const createUser = await this.User
.create(userInfo)
.catch(error => console.error(error))
console.log(createdUser)
// business
// logic
// goes
// here
Note that while this answers the question, it gobbles up the error. The intention must be for the execution to continue and not throw. In this case, it's usually always better to be explicit and return false from catch and check for user:
.catch(error => {
console.error(error);
return false
})
if (!createdUser) // stop operation
In this case, it is better to throw because (1) this operation (creating a user) is not expected to failed, and (2) you are likely not able to continue:
const createUser = await this.User
.create(userInfo)
.catch(error => {
// do what you need with the error
console.error(error)
// maybe send to Datadog or Sentry
// don't gobble up the error
throw error
})
console.log(createdUser)
// business
// logic
// goes
// here
Learning catch doesn't seem like worth it?
The cleanliness benefits may not be apparent above, but it adds up in real-world complex async operations.
As an illustration, besides creating user (this.User.create), we can push notification (this.pushNotification) and send email (this.sendEmail).
this.User.create
this.User.create = async(userInfo) => {
// collect some fb data and do some background check in parallel
const facebookDetails = await retrieveFacebookAsync(userInfo.email)
.catch(error => {
// we can do some special error handling
// and throw back the error
})
const backgroundCheck = await backgroundCheckAsync(userInfo.passportID)
if (backgroundCheck.pass !== true) throw Error('Background check failed')
// now we can insert everything
const createdUser = await Database.insert({ ...userInfo, ...facebookDetails })
return createdUser
}
this.pushNotifcation and this.sendEmail
this.pushNotification = async(userInfo) => {
const pushed = await PushNotificationProvider.send(userInfo)
return pushed
})
this.sendEmail = async(userInfo) => {
const sent = await mail({ to: userInfo.email, message: 'Welcome' })
return sent
})
Compose the operations:
const createdUser = await this.User
.create(userInfo)
.catch(error => {
// handle error
})
// business logic here
return await Promise.all([
this.pushNotification(userInfo),
this.sendEmail(userInfo)
]).catch(error => {
// handle errors caused
// by pushNotification or sendEmail
})
No try/catch. And it's clear what errors you are handling.
I usually use the Promise's catch() function to return an object with an error property on failure.
For example, in your case i'd do:
const createdUser = await this.User.create(userInfo)
.catch(error => { error }); // <--- the added catch
if (Object(createdUser).error) {
console.error(error)
}
If you don't like to keep adding the catch() calls, you can add a helper function to the Function's prototype:
Function.prototype.withCatcher = function withCatcher() {
const result = this.apply(this, arguments);
if (!Object(result).catch) {
throw `${this.name}() must return a Promise when using withCatcher()`;
}
return result.catch(error => ({ error }));
};
And now you'll be able to do:
const createdUser = await this.User.create.withCatcher(userInfo);
if (Object(createdUser).error) {
console.error(createdUser.error);
}
EDIT 03/2020
You can also add a default "catch to an error object" function to the Promise object like so:
Promise.prototype.catchToObj = function catchToObj() {
return this.catch(error => ({ error }));
};
And then use it as follows:
const createdUser = await this.User.create(userInfo).catchToObj();
if (createdUser && createdUser.error) {
console.error(createdUser.error);
}
#Bergi Answer is good, but I think it's not the best way because you have to go back to the old then() method, so i think a better way is to catch the error in the async function
async function someAsyncFunction(){
const createdUser = await this.User.create(userInfo);
console.log(createdUser)
}
someAsyncFunction().catch(console.log);
But what if we have many await in the same function and need to catch every error?
You may declare the to() function
function to(promise) {
return promise.then(data => {
return [null, data];
})
.catch(err => [err]);
}
And then
async function someAsyncFunction(){
let err, createdUser, anotherUser;
[err, createdUser] = await to(this.User.create(userInfo));
if (err) console.log(`Error is ${err}`);
else console.log(`createdUser is ${createdUser}`);
[err, anotherUser] = await to(this.User.create(anotherUserInfo));
if (err) console.log(`Error is ${err}`);
else console.log(`anotherUser is ${anotherUser}`);
}
someAsyncFunction();
When reading this its: "Wait to this.User.create".
Finally you can create the module "to.js" or simply use the await-to-js module.
You can get more information about to function in this post
await this.User.create(userInfo).then(async data => await this.emailService.sendEmail(data.email), async error => await this.sentryService.sendReport(error))
I am trying to make a simple weather application based on Node.js, like this one. My problem is that every mechanism I see is based on promises, and I don't understand the concept.
So, the code I see everywhere is like:
yrno.getWeather(LOCATION).then((weather) => {
weather.getFiveDaySummary().then((data) => console.log('five day summary', data));
weather.getForecastForTime(new Date()).then((data) => console.log('current weather', data));
})
.catch((e) => {
console.log('an error occurred!', e);
});
However, I was unable to find a way to resolve these promises and save the five day summary to a variable for later use.
How do I proceed?
Thanks,
Robin
Assign the Promise returned from yrno.getWeather(LOCATION) call to a variable.
Use Promise.all() to return results from both weather.getFiveDaySummary() and weather.getForecastForTime(new Date()) calls.
Chain .then() to the result of call to get the data at initial and subsequent .then() chained to variable identifier which returned initial Promise values.
let weatherData = yrno.getWeather(LOCATION).then(weather => {
// note `return`, alternatively omit `return` and `{`, `}` at arrow function
// .then(weather => Promise.all(/* parameters */))
return Promise.all([weather.getFiveDaySummary()
, weather.getForecastForTime(new Date())]);
});
weatherData
// `results` is array of `Promise` values returned from `.then()`
// chained to `yrno.getWeather(LOCATION).then((weather)`
.then(results => {
let [fiveDaySummary, forecastForTime] = results;
console.log('five day summary:', fiveDaySummary
, 'current weather:', forecastForTime);
// note `return` statement, here
return results
})
.catch(e => {
// `throw` `e` here if requirement is to chain rejected `Promise`
// else, error is handled here
console.log('an error occurred!', e);
});
// weatherData
// .then(results => { // do stuff with `results` from first `weatherData` call })
// .catch(e => console.log(e));
An alternative to using promises directly is to use await/async.
// weather.js
const yrno = require('yr.no-forecast')({
version: '1.9', // this is the default if not provided,
request: {
// make calls to locationforecast timeout after 15 seconds
timeout: 15000
}
});
const LOCATION = {
// This is Dublin, Ireland
lat: 53.3478,
lon: 6.2597
};
async function getWeather() {
let weather = await yrno.getWeather(LOCATION);
let fiveDaySummary = await weather.getFiveDaySummary();
let forecastForTime = await weather.getForecastForTime(new Date());
return {
fiveDaySummary: fiveDaySummary,
forecastForTime: forecastForTime,
}
}
async function main() {
let report;
try {
report = await getWeather();
} catch (e) {
console.log('an error occurred!', e);
}
// do something else...
if (report != undefined) {
console.log(report); // fiveDaySummary and forecastForTime
}
}
main(); // run it
you can run this (node.js 7) with:
node --harmony-async-await weather
You can use await/async on older targets by using Babel or Typescript to transpile it down for you.
Bonus (based off your comments) - I wouldn't do it this way, but just to show you it can be done:
const http = require('http');
const port = 8080;
http.createServer(
async function (req, res) {
let report = await getWeather(); // see above
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.write("" + JSON.stringify(report.fiveDaySummary));
res.end('Hello World\n');
})
.listen(port);
again with node --harmony-async-await weather or transpile it.