Can we use await and same functions for promises? - javascript

So i have request coming based on that i am building this.request array so i can make calls to urls and request.body that is pushed into array , In below code trying to use RequestResponseHandler.processRequest for both promises but its always going into spec(duplicate call) when i call same function processRequest for other promise , How can i make sure if processRequest is called for spec ignore that if condition and go to PTM
request
{ header: { lineOfBusiness: ["spec","PTM"] } , body: data }
handler.ts
export class RequestResponseHandler {
public static processRequest(data: any, url: string): Promise < any > {
const reqObject: IRequestURL = {}
as IRequestURL;
const lob = data.header.lineOfBusiness;
if (lob[0] === "spec") {
const specUrl = urlConfig + url;
reqObject.url = specUrl;
reqObject.body = data;
}
if (lob[1] === "PTM") {
const ptmUrl = urlConfig + url;
reqObject.url = ptmUrl;
reqObject.body = data;
}
return Promise.resolve(reqObject);
}
}
controllet.ts
const bRetSpec: boolean = await this.specBalanceRequest(request);
const bRetPtm: boolean = await this.ptmBalanceRequest(request);
if (!bRetPtm && !bRetSpec) {
return Promise.reject(new Error("Processing failed"));
}
try {
const __data: IResponse = await makeRequest(this._request);
const resp1 = await another promise to resolve _data[0];
const resp2 = await another promise to resolve _data[1];
return await Promise.all([resp1, resp2]);
} catch (err) {
return Promise.reject(err);
}
private async specBalanceRequest(#Body() request: ExpressRequest): Promise < boolean > {
const specUrl = "/payments";
const reqObject = await RequestResponseHandler.processRequest(request.body, specialtyUrl);
this._request.push(reqObject);
return Promise.resolve(true);
}
private async ptmBalanceRequest(#Body() request: any): Promise < boolean > {
const careURL = "/Order";
const reqObject = await RequestResponseHandler.processRequest(request.body, careURL);
this._request.push(reqObject);
return Promise.resolve(true);
}

One thing that I suggest is to not make unnecessary promise function. Keep it simple as possible.
RequestResponseHandler.processRequest doesn't need to be a promise
export class RequestResponseHandler {
public static processRequest(data: any, url: string): IRequestURL {
const reqObject: IRequestURL = {} as IRequestURL;
const lob = data.header.lineOfBusiness;
// I guess we can improve code below, no diff between "spec" and "ptm"
if (lob[0] === "spec") {
const specUrl = urlConfig + url;
reqObject.url = specUrl;
reqObject.body = data;
}
if (lob[1] === "PTM") {
const ptmUrl = urlConfig + url;
reqObject.url = ptmUrl;
reqObject.body = data;
}
return reqObject; // return value not a promise
}
}
in controller file
const bRetSpec: boolean = this.specBalanceRequest(request); // remove `await`
const bRetPtm: boolean = this.ptmBalanceRequest(request); // remove `await`
if (!bRetPtm && !bRetSpec) {
return Promise.reject(new Error("Processing failed"));
}
try {
const __data: IResponse = await makeRequest(this._request);
// the two promise calls, I guess can be move to be inside promise.all
return await Promise.all([anotherPromiseData0, anotherPromiseData1]);
} catch (err) {
return Promise.reject(err);
}
// make it non promise
private specBalanceRequest(#Body() request: ExpressRequest): void {
const specUrl = "/payments";
const reqObject = RequestResponseHandler.processRequest(request.body, specialtyUrl);
this._request.push(reqObject);
}
// make it non promise
private ptmBalanceRequest(#Body() request: any): void {
const careURL = "/Order";
const reqObject = RequestResponseHandler.processRequest(request.body, careURL);
this._request.push(reqObject);
}

Related

chained await in class chained methods

context: Two javascript classes in separate files, each integrating a different external service and being called in a express.js router.
See "problematic code" below:
route
routes.post('/aws', upload.single('file'), async (req, res) => {
const transcribeParams = JSON.parse(req.body.options)
const bucket = 'bucket-name'
const data = await ( await ( await awsTranscribe.Upload(req.file, bucket)).CreateJob(transcribeParams)).GetJob()
res.send(data)
})
S3 class
class AmazonS3 {
constructor() {
this.Upload = this.Upload
}
async Upload(file, bucket) {
const uploadParams = {
Bucket: bucket,
Body: fs.createReadStream(file.path),
Key: file.filename,
}
this.data = await s3.upload(uploadParams).promise()
return this
}
}
Transcribe class
class Transcribe extends AwsS3 {
constructor() {
super()
this.CreateJob = this.CreateJob
this.GetJob = this.GetJob
}
async CreateJob(params) {
if(this.data?.Location) {
params.Media = { ...params.Media, MediaFileUri: this.data.Location }
}
this.data = await transcribeService.startTranscriptionJob(params).promise()
return this
}
async GetJob(jobName) {
if(this.data?.TranscriptionJob?.TranscriptionJobName) {
jobName = this.data.TranscriptionJob.TranscriptionJobName
}
this.data = await transcribeService.getTranscriptionJob({TranscriptionJobName: jobName}).promise()
return this
}
}
problem: the problem is with the chained awaits in the router file:
await ( await ( await awsTranscribe.Upload...
Yes, it does work, but it would be horrible for another person to maintain this code in the future.
How can i make so it would be just
awsTranscribe.Upload(req.file, bucket).CreateJob(transcribeParams).GetJob() without the .then?
The problem is with the chained awaits in the router file: await ( await ( await awsTranscribe.Upload...
No, that's fine. In particular it would be trivial to refactor it to separate lines:
routes.post('/aws', upload.single('file'), async (req, res) => {
const transcribeParams = JSON.parse(req.body.options)
const bucket = 'bucket-name'
const a = await awsTranscribe.Upload(req.file, bucket);
const b = await b.CreateJob(transcribeParams);
const c = await b.GetJob();
res.send(c);
});
Your actual problem is that a, b, and c all refer to the same object awsTranscribe. Your code would also "work" if it was written
routes.post('/aws', upload.single('file'), async (req, res) => {
const transcribeParams = JSON.parse(req.body.options)
const bucket = 'bucket-name'
await awsTranscribe.Upload(req.file, bucket);
await awsTranscribe.CreateJob(transcribeParams);
await awsTranscribe.GetJob();
res.send(awsTranscribe);
});
The horrible thing is that you are passing your data between these methods through the mutable awsTranscribe.data property - even storing different kinds of data in it at different times! One could change the order of method calls and it would completely break in non-obvious and hard-to-debug ways.
Also it seems that multiple requests share the same awsTranscribe instance. This will not work with concurrent requests. Anything is possible from just "not working" to responding with the job data from a different user (request)! You absolutely need to fix that, then look at ugly syntax later.
What you really should do is get rid of the classes. There's no reason to use stateful objects here, this is plain procedural code. Write simple functions, taking parameters and returning values:
export async function uploadFile(file, bucket) {
const uploadParams = {
Bucket: bucket,
Body: fs.createReadStream(file.path),
Key: file.filename,
};
const data = s3.upload(uploadParams).promise();
return data.Location;
}
export async function createTranscriptionJob(location, params) {
params = {
...params,
Media: {
...params.Media,
MediaFileUri: location,
},
};
const data = await transcribeService.startTranscriptionJob(params).promise();
return data.TranscriptionJob;
}
async function getTranscriptionJob(job) {
const jobName = job.TranscriptionJobName;
return transcribeService.getTranscriptionJob({TranscriptionJobName: jobName}).promise();
}
Then you can import and call them as
routes.post('/aws', upload.single('file'), async (req, res) => {
const transcribeParams = JSON.parse(req.body.options)
const bucket = 'bucket-name'
const location = await uploadFile(req.file, bucket);
const job = await createTranscriptionJob(location, transcribeParams);
const data = await getTranscriptionJob(job);
res.send(c);
});
I got interested in whether it was possible to take an object with several async methods and somehow make them automatically chainable. Well, you can:
function chain(obj, methodsArray) {
if (!methodsArray || !methodsArray.length) {
throw new Error("methodsArray argument must be array of chainable method names");
}
const methods = new Set(methodsArray);
let lastPromise = Promise.resolve();
const proxy = new Proxy(obj, {
get(target, prop, receiver) {
if (prop === "_promise") {
return function() {
return lastPromise;
}
}
const val = Reflect.get(target, prop, receiver);
if (typeof val !== "function" || !methods.has(prop)) {
// no chaining if it's not a function
// or it's not listed as a chainable method
return val;
} else {
// return a stub function
return function(...args) {
// chain a function call
lastPromise = lastPromise.then(() => {
return val.apply(obj, args);
//return Reflect.apply(val, obj, ...args);
});
return proxy;
}
}
}
});
return proxy;
}
function delay(t) {
return new Promise(resolve => {
setTimeout(resolve, t);
});
}
function log(...args) {
if (!log.start) {
log.start = Date.now();
}
const delta = Date.now() - log.start;
const deltaPad = (delta + "").padStart(6, "0");
console.log(`${deltaPad}: `, ...args)
}
class Transcribe {
constructor() {
this.greeting = "Hello";
}
async createJob(params) {
log(`createJob: ${this.greeting}`);
return delay(200);
}
async getJob(jobName) {
log(`getJob: ${this.greeting}`);
return delay(100);
}
}
const t = new Transcribe();
const obj = chain(t, ["getJob", "createJob"]);
log("begin");
obj.createJob().getJob()._promise().then(() => {
log("end");
});
There's a placeholder for your Transcribe class that has two asynchronous methods that return a promise.
Then, there's a chain() function that returns a proxy to an object that makes a set of passed in method names be chainable which allows you to then do something like this:
const t = new Transcribe();
// make chainable proxy
const obj = chain(t, ["getJob", "createJob"]);
obj.createJob().getJob()
or
await obj.createJob().getJob()._promise()
I wouldn't necessarily say this is production-ready code, but it is an interesting feasibility demonstration and (for me) a chance to learn more about a Javascript proxy object.
Here's a different approach that (instead of the proxy object) adds method stubs to a promise to make things chainable:
function chain(orig, methodsArray) {
let masterP = Promise.resolve();
function addMethods(dest) {
for (const m of methodsArray) {
dest[m] = function(...args) {
// chain onto master promise to force sequencing
masterP = masterP.then(result => {
return orig[m].apply(orig, ...args);
});
// add methods to the latest promise befor returning it
addMethods(masterP);
return masterP;
}
}
}
// add method to our returned promise
addMethods(masterP);
return masterP;
}
function delay(t) {
return new Promise(resolve => {
setTimeout(resolve, t);
});
}
function log(...args) {
if (!log.start) {
log.start = Date.now();
}
const delta = Date.now() - log.start;
const deltaPad = (delta + "").padStart(6, "0");
console.log(`${deltaPad}: `, ...args)
}
class Transcribe {
constructor() {
this.greeting = "Hello";
this.cntr = 0;
}
async createJob(params) {
log(`createJob: ${this.greeting}`);
++this.cntr;
return delay(200);
}
async getJob(jobName) {
log(`getJob: ${this.greeting}`);
++this.cntr;
return delay(100);
}
}
const t = new Transcribe();
log("begin");
chain(t, ["getJob", "createJob"]).createJob().getJob().then(() => {
log(`cntr = ${t.cntr}`);
log("end");
});
Since this returns an actual promise (with additional methods attached), you can directly use .then() or await with it without the separate ._promise() that the first implementation required.
So, you can now do something like this:
const t = new Transcribe();
chain(t, ["getJob", "createJob"]).createJob().getJob().then(() => {
log(`cntr = ${t.cntr}`);
});
or:
const t = new Transcribe();
await chain(t, ["getJob", "createJob"]).createJob().getJob();
log(`cntr = ${t.cntr}`);
And, here's a third version where it creates a thenable object (a pseudo-promise) with the added methods on it (if it bothers you to add methods to an existing promise):
function chain(orig, methodsArray) {
if (!methodsArray || !methodsArray.length) {
throw new Error("methodsArray argument must be array of chainable method names");
}
let masterP = Promise.resolve();
function makeThenable() {
let obj = {};
for (const m of methodsArray) {
obj[m] = function(...args) {
// chain onto master promise to force sequencing
masterP = masterP.then(result => {
return orig[m].apply(orig, ...args);
});
return makeThenable();
}
}
obj.then = function(onFulfill, onReject) {
return masterP.then(onFulfill, onReject);
}
obj.catch = function(onReject) {
return masterP.catch(onReject);
}
obj.finally = function(onFinally) {
return masterP.finally(onFinally);
}
return obj;
}
return makeThenable();
}
function delay(t) {
return new Promise(resolve => {
setTimeout(resolve, t);
});
}
function log(...args) {
if (!log.start) {
log.start = Date.now();
}
const delta = Date.now() - log.start;
const deltaPad = (delta + "").padStart(6, "0");
console.log(`${deltaPad}: `, ...args)
}
class Transcribe {
constructor() {
this.greeting = "Hello";
this.cntr = 0;
}
async createJob(params) {
log(`createJob: ${this.greeting}`);
++this.cntr;
return delay(200);
}
async getJob(jobName) {
log(`getJob: ${this.greeting}`);
++this.cntr;
return delay(100);
}
}
const t = new Transcribe();
log("begin");
chain(t, ["getJob", "createJob"]).createJob().getJob().then(() => {
log(`cntr = ${t.cntr}`);
log("end");
});

Async Function returns promise

I have a function that returns the guilds prefix in Discord.JS:
getprefix.js:
const GuildSchema = require("../Database/Models/GuildConfigs");
const { DEFAULT } = require("./config");
const getprefix = async (id) => {
const guildConfigs = await GuildSchema.findOne({
GuildID: id,
});
let PREFIX = DEFAULT;
if (guildConfigs && guildConfigs?.Prefix) {
PREFIX = guildConfigs?.Prefix;
}
};
module.exports = { getprefix };
I call the function in another file using this:
let prefix = getprefix(message.guild.id);
prefix.then(() => {
console.log(prefix);
});
The problem is it returns this in the console:
Promise { '!' }
Is it possible to just return the actual prefix that is inside the quotes with out the Promise?
Yes, but you must return the value from the async function.
getprefix.js:
const GuildSchema = require("../Database/Models/GuildConfigs");
const { DEFAULT } = require("./config");
const getprefix = async (id) => {
const guildConfigs = await GuildSchema.findOne({
GuildID: id,
});
let PREFIX = DEFAULT;
if (guildConfigs && guildConfigs?.Prefix) {
PREFIX = guildConfigs?.Prefix;
}
return PREFIX;
};
module.exports = { getprefix };
and change the call:
let prefix = getprefix(message.guild.id);
prefix.then((value) => {
console.log(value);
});
First you must return a value from your async function called getprefix. Secondly you must console.log the result of the promise returned by getprefix function instead of the promise itself :
const getprefix = async (id) => {
const guildConfigs = await GuildSchema.findOne({GuildID: id});
if (!guildConfigs || !guildConfigs.Prefix) {
return DEFAULT;
}
return guildConfigs.Prefix;
};
getprefix(message.guild.id).then(prefix => console.log(prefix));

Error while returning value from async dns.lookup() function

I'm writing a function to verify a url using dns.lookup() function as defined below:
const dns = require('dns');
const verifyURL = (url) => {
const protocolRegEx = /^https?:\/\/(.*)/i;
const hostnameRegEx = /^([a-z0-9\-_]+\.)+[a-z0-9\-_]+/i;
if (url.match(/\/$/i)) {
url = url.slice(0,-1);
}
const protocol = url.match(protocolRegEx);
if (!protocol) {
return false;
}
const hostname = protocol[1].match(hostnameRegEx);
if (hostname) {
dns.lookup(hostname[0], (err, adderss) => {
if (err) return false;
return adderss;
})
}
}
I'm trying to return either true or false from inside of the callback passed to the dns.lookup() function.
(async () => {
let x = await verifyURL('https://stackoverflow.com/')
console.log(x);
})();
But everytime I run this code I get undefined as return value.
I already tried this answer, but it's not working.
Please help me. Thank you!!
You are awaiting a function, verifyURL, which does not return a promise.
One option is to modify your verifyURL function to return a promise.
const verifyURL = (url) => {
const protocolRegEx = /^https?:\/\/(.*)/i;
const hostnameRegEx = /^([a-z0-9\-_]+\.)+[a-z0-9\-_]+/i;
if (url.match(/\/$/i)) {
url = url.slice(0,-1);
}
const protocol = url.match(protocolRegEx);
if (!protocol) {
return false;
}
const hostname = protocol[1].match(hostnameRegEx);
//verify this! I add this to return false if !hostname
if (!hostname) {
return false
}
return new Promise((resolve, reject) => {
dns.lookup(hostname[0], (err, address) => {
if (err) {
return reject(err);
}
// Here I resolve to address. You can resolve to true as you mention in your post.
return resolve(address)
});
});
}
Also you should handle the rejection of the promise.
E.g. looking for https://stackoverflow2.com will give you an error "getaddrinfo ENOTFOUND stackoverflow2.com"
(async () => {
try {
let x = await verifyURL('https://stackoverflow.com')
console.log(x);
} catch (e) {
console.error(e.message)
}
})();
Another option is to use dns promise api

Node.js handle responses from chained promises

I have 3 functions and each of them return a promise. How can I assign the response from each promise to a defined object?
This is my code:
const runResponse = {
webAudits: [],
webJourneys: [],
appJourneys: []
};
webAuditsFailures(req)
.then(
appJourneysFailures(req)
)
.then(
webJourneysFailures(req)
).then(
res.status(201).json({ reports: runResponse })
);
This is what I've tried:
webAuditsFailures(req)
.then(
(response) => {
runResponse.webAudits = response
},
appJourneysFailures(req)
)
.then(
(response) => {
runResponse.appJourneys = response
},
webJourneysFailures(req)
).then(
(response) => {
runResponse.webJourneys = response
},
res.status(201).json({ reports: runResponse })
);
But it doesn't works as expected because the webAuditsFailures is called again even if it didn't end and I don't understand why...
These are other failed attempts to fix this:
Using await
const webAudits = await webAuditsFailures(req);
const appJourneys = await appJourneysFailures(req);
const webJourneys = await webJourneysFailures(req);
runResponse.webAudits = webAudits;
runResponse.webJourneys = webJourneys;
runResponse.appJourneys = appJourneys;
The same thing happens with this:
const webAudits = await webAuditsFailures(req);
runResponse.webAudits = webAudits;
Using the co module:
co(function* () {
var runResponse = yield {
webAudits: webAuditsFailures(req),
webJourneys: appJourneysFailures(req),
appJourneys: webJourneysFailures(req)
};
res.status(201).json({ reports: runResponse });
});
Using Promise.all:
Promise.all([webAuditsFailures(req), appJourneysFailures(req),
webJourneysFailures(req)])
.then(function(allData) {
res.status(201).json({ reports: allData });
});
This is the webAuditsFailures function, which sequentially calls another functions that return a promise
export default async (req) => {
const report = req.body.webAudits;
const def = deferred();
if(report.length > 0) {
var reportList = [];
for(const [reportIndex, item] of report.entries()) {
for(const [runIndex, run] of item.runs.entries()) {
const result = await waComplianceBusiness(req, run.id);
var failureList = [];
if(result.data.overviews) {
const compliance = result.data.overviews[0].compliance;
if(compliance) {
for(const [index, rule] of compliance.entries()) {
const response = await waRuleOverview(req, run.id, rule.id);
const failedConditions = response.data.failedConditions;
const ruleName = response.data.ruleName;
if(response.data.pagesFailed > 0) {
for(const [condIndex, condition] of failedConditions.entries()) {
const request = {
itemId: condition.conditionResult.id,
itemType: condition.conditionResult.idType,
parentId: condition.conditionResult.parentId,
parentType: condition.conditionResult.parentType
}
const body = {
runId: run.id,
ruleId: rule.id,
payload: request
}
waConditionOverview(req, body).done(response => {
// do stuff here
});
}
}
}
if(failureList.length > 0) {
item.runs[runIndex].failures = failureList;
}
}
}
}
}
def.resolve(report);
return def.promise
}
else {
return [];
}
}
This is the problem:
waConditionOverview(req, body).done(response => {
// do stuff here
});
You're performing an async action but not waiting for the result. Don't use the deferred model - use util.promisify for callbacks.
In addition, I warmly recommend not mutating the request/resposne like this but storing the information in objects and returning those.
Here is how you'd write the code:
export default async (req) => {
const report = req.body.webAudits;
if(report.length === 0) return;
const runs = Array.from(report.entries(), ([i, item]) => item.runs.entries());
for (const [_, run] of runs) {
const result = await waComplianceBusiness(req, run.id);
var failureList = [];
if (!result.data.overviews) {
continue;
}
const compliance = result.data.overviews[0].compliance;
if(!compliance) {
continue;
}
for(const [_, rule] of compliance.entries()) {
const response = await waRuleOverview(req, run.id, rule.id);
const { failedConditions, ruleName} = response.data;
if(failureList.length > 0) {
item.runs[runIndex].failures = failureList;
}
if(response.data.pagesFailed === 0) continue;
for(const [_, condition] of failedConditions.entries()) {
const request = {
itemId: condition.conditionResult.id,
itemType: condition.conditionResult.idType,
parentId: condition.conditionResult.parentId,
parentType: condition.conditionResult.parentType
}
const body = { runId: run.id, ruleId: rule.id, payload: request}
const reponse = await waConditionOverview(req, body);
// do stuff here
// update response
// update report, so next time we try it's updated and doesn't return empty;
}
}
}
return report;
}
In a promise chain, the current .then() should return a promise. The result of this promise will be passed to the next .then():
webAuditsFailures(req)
.then((response) => {
runResponse.webAudits = response;
return appJourneysFailures(req); // return a promise
})
.then((response) => { // response contains the result of the promise
runResponse.appJourneys = response;
return webJourneysFailures(req);
})
.then((response) => {
runResponse.webJourneys = response;
res.status(201).json({ reports: runResponse });
});
Depending on what .json() in the last .then() does, you should return that as well if there are other .then()s in the promise chain.

Async / Await, how do I force my function to wait until promise is resolved before it continues?

I would like my function to execute until the nextPageToken is null. When I execute the function the first time, it waits for the promise to be resolved. However as soon there is a nextPageToken present on the response, the function does not wait for the response and a stack overflow occurs.
It seems that f() is not suspended on the when await p.then() is called.
Am I totally misunderstanding how async/await works?
Any help would be greatly appreciated...
public apiResult2(path: string, objectName: string, params: any = { }) {
let returnArray = [];
const p = new Promise<any> ((resolve, reject) => {
gapi.load('auth2:client', () => {
gapi.client.request({
path: path,
params: params
}).then(response => {
// resolve this promise with the first key in the response object.
resolve(response.result);
}, reason => {
console.log(reason);
reject(reason.result);
});
});
});
let f = async () => {
let nextPageToken = null;
do {
let r = await p.then(result => {
if (result.hasOwnProperty(objectName)) {
for (let obj of result[objectName]) {
returnArray.push(obj);
}
}
if (result.hasOwnProperty('nextPageToken')) {
params.nextPageToken = result.nextPageToken;
return result.nextPageToken;
// nextPageToken = result.nextPageToken;
} else {
params.nextPageToken = null;
return null;
// nextPageToken = null;
}
});
nextPageToken = r;
console.log(r);
} while (nextPageToken);
};
f();
return returnArray;
}
If your function needs to "await" some async call, then it must also be async. Your function apiResult2 is not going to wait for f to be finished, in order to return returnArray.
EDIT:
The main issue here is that you are trying to reuse the promise p to make different requests, but this is not possible. The promise p will be initialized with the parameters for the first request, and all the calls to p.then will be fulfilled with the same result: the response for the first page request.
I made some small changes to your code, and got it working with a mocked interface:
const apiResult2 = async (path: string, objectName: string, params: any = { }) => {
const requestPage = async () => new Promise<any> ((resolve, reject) => {
gapi.load('auth2:client', () => {
gapi.client.request({
path: path,
params: params
}).then(response => {
// resolve this promise with the first key in the response object.
resolve(response.result);
}, reason => {
console.log(reason);
reject(reason.result);
});
});
});
let returnArray: string[] = [];
do {
const page = await requestPage();
if (page.hasOwnProperty(objectName)) {
for (let obj of page[objectName]) {
returnArray.push(obj);
}
}
if (page.hasOwnProperty('nextPageToken')) {
params.nextPageToken = page.nextPageToken;
} else {
params.nextPageToken = null;
}
} while (params.nextPageToken);
return returnArray;
}
Usage example:
apiResult2(path, objectName, params).then(
result => console.log(result),
err => console.log('error', err)
);

Categories