How to handle secondary await usage in Promise.all then callback - javascript

I have two calls that resolve promises to retrieve data from indexeddb, if this fails to load data I would like it to retrieve data from the API.
export const retrieveServiceRequest = createAsyncThunk(
'csm/retrieveServiceRequest',
async (
retrievalPayload: any
) => {
try {
if (retrievalPayload.userId && retrievalPayload.serviceRequestTag) {
const userServiceRequestKey: string = retrievalPayload.userId + '_' + retrievalPayload.serviceRequestTag;
await Promise.all([
csmIndexedDbService.getCSMData(CSM_Stores.CSM_SR_EDITED, userServiceRequestKey),
csmIndexedDbService.getCSMData(CSM_Stores.CSM_SR_UNEDITED, userServiceRequestKey)
])
.then (requestDataArray => {
if (requestDataArray.length > 0 && requestDataArray[0]){
return { serviceRequest: requestDataArray[0], serviceRequestOriginal: requestDataArray[1], isDirty: true, addToStore: true }
} else {
return await csmRequestDataService.getServiceRequest(retrievalPayload.serviceRequestId);
}
})
.catch (error =>
console.log(error)
);
}
} catch (error) {
console.log('Error: ', error);
}
}
);
The piece that I am unsure about is:
return await csmRequestDataService.getServiceRequest(retrievalPayload.serviceRequestId);
Awaiting the API gives an error:
'await' expressions are only allowed within async functions and at the
top levels of modules
How should I handle await for a second round?

You could make the then handler async, but it's better not to mix the two styles. Assign the result of the awaited expression to a variable, then use it directly, without the need for a then handler.
export const retrieveServiceRequest = createAsyncThunk(
'csm/retrieveServiceRequest',
async (
retrievalPayload: any
) => {
try {
if (retrievalPayload.userId && retrievalPayload.serviceRequestTag) {
const userServiceRequestKey: string = retrievalPayload.userId + '_' + retrievalPayload.serviceRequestTag;
// assign to variable .
const requestDataArray = await Promise.all([
csmIndexedDbService.getCSMData(CSM_Stores.CSM_SR_EDITED, userServiceRequestKey),
csmIndexedDbService.getCSMData(CSM_Stores.CSM_SR_UNEDITED, userServiceRequestKey)
])
// move out of .then handler
if (requestDataArray.length > 0 && requestDataArray[0]){
return { serviceRequest: requestDataArray[0], serviceRequestOriginal: requestDataArray[1], isDirty: true, addToStore: true }
} else {
return await csmRequestDataService.getServiceRequest(retrievalPayload.serviceRequestId);
}
}
} catch (error) {
console.log('Error: ', error);
}
}
);
Don't swallow errors with try..catch and console.error, simply let them bubble up -
export const retrieveServiceRequest = createAsyncThunk(
'csm/retrieveServiceRequest',
async (
retrievalPayload: any
) => {
if (retrievalPayload.userId && retrievalPayload.serviceRequestTag) {
const userServiceRequestKey: string = retrievalPayload.userId + '_' + retrievalPayload.serviceRequestTag;
const requestDataArray = await Promise.all([
csmIndexedDbService.getCSMData(CSM_Stores.CSM_SR_EDITED, userServiceRequestKey),
csmIndexedDbService.getCSMData(CSM_Stores.CSM_SR_UNEDITED, userServiceRequestKey)
])
if (requestDataArray.length > 0 && requestDataArray[0]){
return { serviceRequest: requestDataArray[0], serviceRequestOriginal: requestDataArray[1], isDirty: true, addToStore: true }
} else {
return await csmRequestDataService.getServiceRequest(retrievalPayload.serviceRequestId);
}
}
}
);
The caller is allowed to handle them however they wish -
retrieveServiceRequest.catch(console.error)
Finally avoid return-await ... anti-pattern. All async functions implicitly return a promise -
export const retrieveServiceRequest = createAsyncThunk(
'csm/retrieveServiceRequest',
async (
retrievalPayload: any
) => {
if (retrievalPayload.userId && retrievalPayload.serviceRequestTag) {
const userServiceRequestKey: string = retrievalPayload.userId + '_' + retrievalPayload.serviceRequestTag;
const requestDataArray = await Promise.all([
csmIndexedDbService.getCSMData(CSM_Stores.CSM_SR_EDITED, userServiceRequestKey),
csmIndexedDbService.getCSMData(CSM_Stores.CSM_SR_UNEDITED, userServiceRequestKey)
])
if (requestDataArray.length > 0 && requestDataArray[0]){
return { serviceRequest: requestDataArray[0], serviceRequestOriginal: requestDataArray[1], isDirty: true, addToStore: true }
} else {
// this does the exact same thing
return csmRequestDataService.getServiceRequest(retrievalPayload.serviceRequestId);
}
}
}
);
Hopefully this helps you see the elegance and simplicity of the async..await pattern. When used correctly, it allows the writer to seamlessly and effortlessly transition between sychronous and asynchronous control flow.

Related

Replacing then statements with try/catch

I'm trying to remove the then statements from the following piece of code and then replace all the catches with try/catch statements. I'm having some issues knowing what to do with the then statements.
export class WelcomePageContribution implements IWorkbenchContribution {
constructor(
#IInstantiationService instantiationService: IInstantiationService,
#IConfigurationService configurationService: IConfigurationService,
#IEditorService editorService: IEditorService,
#IBackupFileService backupFileService: IBackupFileService,
#IFileService fileService: IFileService,
#IWorkspaceContextService contextService: IWorkspaceContextService,
#ILifecycleService lifecycleService: ILifecycleService,
#ICommandService private readonly commandService: ICommandService,
) {
const enabled = isWelcomePageEnabled(configurationService, contextService);
if (enabled && lifecycleService.startupKind !== StartupKind.ReloadedWindow) {
backupFileService.hasBackups().then(hasBackups => {
const activeEditor = editorService.activeEditor;
if (!activeEditor && !hasBackups) {
const openWithReadme = configurationService.getValue(configurationKey) === 'readme';
if (openWithReadme) {
return Promise.all(contextService.getWorkspace().folders.map(folder => {
const folderUri = folder.uri;
return fileService.resolve(folderUri)
.then(folder => {
const files = folder.children ? folder.children.map(child => child.name) : [];
const file = arrays.find(files.sort(), file => strings.startsWith(file.toLowerCase(), 'readme'));
if (file) {
return joinPath(folderUri, file);
}
return undefined;
}, onUnexpectedError);
})).then(arrays.coalesce)
.then<any>(readmes => {
if (!editorService.activeEditor) {
if (readmes.length) {
const isMarkDown = (readme: URI) => strings.endsWith(readme.path.toLowerCase(), '.md');
return Promise.all([
this.commandService.executeCommand('markdown.showPreview', null, readmes.filter(isMarkDown), { locked: true }),
editorService.openEditors(readmes.filter(readme => !isMarkDown(readme))
.map(readme => ({ resource: readme }))),
]);
} else {
return instantiationService.createInstance(WelcomePage).openEditor();
}
}
return undefined;
});
} else {
return instantiationService.createInstance(WelcomePage).openEditor();
}
}
return undefined;
}).then(undefined, onUnexpectedError);
}
}
}
so that the entire thing reads more like this..
const enabled = await isWelcomePageEnabled(configurationService, contextService);
if (enabled && lifecycleService.startupKind !== StartupKind.ReloadedWindow) {
const hasBackups = await backupFileService.hasBackups();
const activeEditor = editorService.activeEditor;
if (!activeEditor && !hasBackups) {
const openWithReadme = configurationService.getValue(configurationKey) === 'readme';
if (openWithReadme) {
...
It looks like you're on the right track with your second code block. then is called on a promise, so instead of using then you would await the function then was called on, save it to a variable, and then move the code that was in the callback to then below the await at the same indentation level. Whenever you await, you can wrap it in a try/catch and put what would have been in the catch callback inside of the catch block.
So for example
fetchData().then(data => {
console.log(data)
}).catch(err => {
console.error(err)
})
becomes
try {
const data = await fetchData()
console.log(data)
}
catch (err) {
console.error(err)
}
The complication in your example is that the code is in a class constructor, and those can't be async.

Can not return from a function

I have a function that looks like following
export const checkForAvailableAgent = (topicId, serviceUrl, serviceId) => {
const serviceInfo = new window.adiaLive.ServiceInfo({
topicId: topicId, // set here the topicId which you want listen for
OnError: e => {
// react to error message (optional)
console.log("error: ", e);
},
OnServiceStateChange: e => {
if (e.ConnectedAdvisers > 0) {
// there are advisers online for given topicId
console.log("studio available");
return true;
} else {
console.log("studio not available");
return false;
}
}
});
serviceInfo.connect(serviceUrl, serviceId);
};
however the return statements don't return anything when I use the function in the following manner
useEffect(() => {
const agent = checkForAvailableAgent(
`sales_${i18n.language}`,
"https://linktoserviceurl",
"serviceid"
);
// console.log("studio available is: ", agent);
}, []);
the console.log massages appear but the return statement is undefined.
any help would be appreciated.
You can not return from a callback function, as it is running asynchronously and you are not waiting for it to have a result ready.
You can however make the function itself async by returning a Promise instead of the actual result and wait until the Promise has a result ready (e.g. it is resolved):
export const checkForAvailableAgent = (topicId, serviceUrl, serviceId) => {
return new Promise((resolve, reject) => {
const serviceInfo = new window.adiaLive.ServiceInfo({
topicId: topicId, // set here the topicId which you want listen for
OnError: e => {
// react to error message (optional)
console.log("error: ", e);
reject(); // reject on failure
},
OnServiceStateChange: e => {
if (e.ConnectedAdvisers > 0) {
// there are advisers online for given topicId
console.log("studio available");
resolve(true); // resolve instead of return
} else {
console.log("studio not available");
resolve(false);
}
}
});
serviceInfo.connect(serviceUrl, serviceId);
})
};
useEffect(() => {
checkForAvailableAgent(
`sales_${i18n.language}`,
"https://linktoserviceurl",
"serviceid"
).then((agent) => { // then callback is called when the promise resolved
console.log("studio available is: ", agent);
}).catch(error => { // catch is called when promise got rejected
console.log('An error happened');
});
}, []);
The function servceInfo.OnServiceStateChange is a function into the object (seems to be an event).
I'd suggest declaring a variable on the checkForAvailableAgent like connected and change it's value when the event is called.
Then access it using checkForAvailableAgent.connected.
A version with async/await and try/catch
export const checkForAvailableAgent = (topicId, serviceUrl, serviceId) => {
return new Promise((resolve, reject) => {
const serviceInfo = new window.adiaLive.ServiceInfo({
topicId: topicId,
OnError: reject,
OnServiceStateChange: e => resolve(e.ConnectedAdvisers > 0)
});
serviceInfo.connect(serviceUrl, serviceId);
})
};
useEffect(() => {
(async () => {
try {
const isAvailable = await checkForAvailableAgent(
`sales_${i18n.language}`,
"https://linktoserviceurl",
"serviceid"
);
// console.log("Result", isAvailable)
} catch(e) {
console.error(e)
}
})()
// console.log("studio available is: ", agent);
}, []);
There are 2 possible reasons
you are not returning anything from checkForAvailableAgent.
After returning from the checkForAvailableAgent, it might be asynchronous function. You can use async & await.

Confused between promise and async/awaits

I'm a novice in Node JS. I practice promise and I successfully used it. What I understand is with using a promise you can hold the output and send resolve and reject. I used in database operation.
Then someone suggested me to use async/awaits. So here is my code which ran successfully the first time.
shop.js file
const models = require("../models");
const shopModel = models.Shop;
exports.checkShop = function(shopName) {
return new Promise((reslove, reject) => {
shopModel
.findOne({ where: { shop: shopName } })
.then(rs => {
if (rs) {
reslove(rs);
}
})
.catch(err => {
reject(err.toString());
});
});
};
And the file where i called this
const shopController = require("./shop");
exports.getInstall = function(req, res) {
const shop = req.body.shop;
if (!cn(shop)) {
shopController
.checkShop(shop)
.then(
shopCheck =>
function() {
if (shopCheck) {
res.send(`Welcome back ${shopCheck.shop}`);
} else {
//my else stuff
}
}
)
.catch(
e =>
function() {
res.state(500).send(e);
}
);
} else {
return res
.status(400)
.send(
"Missing shop parameter. Please add ?shop=your-development-shop.myshopify.com to your request"
);
}
};
And this is how I tried to replace it with async/awaits. But it does not work.
exports.checkShop = async function(shopName) {
try{
var rs = await shopModel.findOne({ where: { shop: shopName } });
if(rs){
return rs;
}
else{
return false;
}
}
catch(e){
return Promise.reject(new Error(400));
}
};
And the other file
exports.getInstall = function(req, res) {
const shop = req.body.shop;
if (!cn(shop)) {
var shopCheck = shopController.checkShop(shop);
try {
if (shopCheck) {
res.send(`Welcome back ${shopCheck.shop}`);
} else {
// else stuff
}
} catch (e) {
res.state(500).send(e);
}
} else {
return res
.status(400)
.send(
"Missing shop parameter. Please add ?shop=your-development-shop.myshopify.com to your request"
);
}
};
Every function with the async keyword before it will (explicitly or implicitly) return a promise.
So when you call shopController.checkShop you will either have to do something like
shopController.checkShop().then(.... )
or make getInstall an async function as well so that you can use await inside it.
exports.getInstall = async function(req, res) {
// other code here..
const result = await shopController.checkShop(shop);
//..
}
Edit:
If you want to make getInstall async and use await on checkShop you will have to catch the potential rejection using try {} catch like you did in checkShop.

how to handle new Error() in node.js using ES6 Symbol?

I am creating a endpoint in node.js using ES6 Symbol. Example
// ES6 Symbol Method
const taskCreationMethod = {
[Symbol.taskMethod]() {
return {
storeCheckFunc: async function(storeId, employeeId) {
let store = await resourceModel["stores"].findById(storeId).populate(references["stores"]);
if(!store) {
return new Error("Store not found");
}
let employeeCheck = _.find(store.employees, (empObj) => {
return empObj._id == employeeId
})
if(!employeeCheck) {
return new Error("Employee not found");
}
return employeeCheck;
}
};
}
}
//end point
export const taskCreation = async(req, res) => {
const storeCheck = await taskCreationMethod[Symbol.taskMethod]().storeCheckFunc(req.body.store, req.body.assigned_to);
// here How can I handle return with Error Response?
}
You need to throw that error not just return it if you want to use the mechanisms of error handling. The thrown error will become a rejected promise which you can then handle with .catch() directly on the promise or with try/catch if you are using it in an async function. Here's a simplified example:
function populate() {
// always resolves to undefined
return Promise.resolve(undefined)
}
const taskCreationMethod = {
someMethod() {
return {
storeCheckFunc: async function() {
let store = await populate() // always resolves undefined
if (!store) { // so it always fails
throw new Error("Store not found"); // throw error
}
}
};
}
}
// regular promise then().catch()
taskCreationMethod.someMethod().storeCheckFunc()
.then(res => console.log(res))
.catch(err => console.log("Error:", err.message)) // catch
// OR … async function
async function runit() {
try {
let s = await taskCreationMethod.someMethod().storeCheckFunc()
} catch (err) {
console.log("Error:", err.message)
}
}
runit()

Access all variables within a function even when error is thrown?

Below I have a main function. This function has many other variables declared in it and runs many functions. If an error occurs within the function I can catch this error but within my catch I do not have the value for the main function. I'm looking for a way to have access to all the variables even when an error is thrown.
export async function main ({alpha, beta, gamma}) {
let one = await doSomething(alpha)
let two = await doSomethingElse(one, beta)
return {one, two}
}
export async function store (db) {
await db.insert(data)
}
export async function usage (db, data) {
try {
let operation = await main(data)
await store (db, operation)
return operation
} catch (e) {
// here we don't have access to operation variables :(
await store(db, {}, e.message)
throw e
}
}
The only reasonable way i've found to do this is to create a class where each value in the main function.
import { get } from 'lodash'
export class Operation () {
constructor(db){
this.db = db
}
main({alpha, beta, gamma}) {
this.one = await doSomething(alpha)
this.two = await doSomethingElse(one, beta)
return {one: this.one, two: this.two}
}
init(data) {
try {
let main = await this.main(data)
await this.store(main)
return main
} catch (e) {
await this.store()
throw e
}
}
store(data = {}, errorMessage) {
if (!data.one) data.one = get(this, 'one') || null
if (!data.two) data.two = get(this, 'two') || null
if (errorMessage) data.errMessage = errorMessage
return await this.db.insert(data)
}
}
How can I have access to all the variables within a function even when an error is thrown?
The simple solution is to use var to declare a function-scoped variable instead of one that is scoped to the let block.
However, you really should just use two try/catch statements, for you are trying to catch two different errors and going to handle them differently:
try {
let operation = await main(data)
try {
await store (db, operation)
} catch(e) {
// here we do have access to the `operation` variable :)
}
return operation
} catch (e) { // failed to create operation (or the inner catch did rethrow)
await store(db, {}, e.message)
throw e
}
I created this class and wrapper function alwaysRunner. which takes two function arguments and will always provide the available variables to the secondary function.
export class AlwaysRunner {
constructor(operation, reguardless, optional = {}){
Object.assign(this, optional)
this.operation = operation.bind(this)
this.reguardless = reguardless.bind(this)
}
async init(data) {
let operation = this.operation
let reguardless = this.reguardless
let optional = this.optional
delete this.operation
delete this.reguardless
delete this.optional
try {
let main = await operation(data)
await reguardless(this)
return main
} catch (error) {
await reguardless({...this, error})
throw error
}
}
}
export async function alwaysRunner(operation, reguardless, optional) {
return await new AlwaysRunner(operation, reguardless, optional).init()
}
Here's some tests on how it works.
describe('alwaysRunner', () => {
it('should work', async () => {
let ran = [false, false]
await alwaysRunner(function () {
ran[0] = true
this.alpha = true
this.beta = true
return this
}, function (values) {
ran[1] = true
assert.equal(values.alpha, true)
assert.equal(values.beta, true)
assert.equal(values.error, undefined)
}).should.not.be.rejected
assert.deepEqual(ran, [true, true])
})
it('should work with thrown error', async () => {
let ran = [false, false]
await alwaysRunner(function () {
ran[0] = true
this.alpha = true
throw new Error('some error')
this.beta = true
}, function (values) {
ran[1] = true
assert.equal(values.alpha, true)
assert.equal(values.beta, undefined)
assert.equal(values.error, new Error('some error'))
}).should.be.rejected
assert.deepEqual(ran, [true, true])
})
})

Categories