Async await with promises - meteor

I would like to know why this code using async and await does not work.
// I promisified `github.users.get` which is asynchronous
function getUserData() {
return new Promise(function (resolve, reject) {
github.users.get({}, function (err, res) {
if (err) {
reject(err);
} else {
resolve(res)
}
});
});
}
// Similar to above
function getUserEmails() {
return new Promise(function (resolve, reject) {
github.users.getEmails({}, function (err, res) {
if (err) {
reject(err);
} else {
resolve(res)
}
});
});
}
(async function () {
let github = new GithubAPI({version: '3.0.0'});
github.authenticate({
type: 'oauth',
token: // auth token
});
let userData = await getUserData(); // stuck
let emails = await getUserEmails();
// do something
}());
The code never continues beyond let userData = await getUserData();. It is stuck there.
What am I doing wrong? I am using Meteor 1.3.1.

Related

waitForResponse does not work in the cluster.tasks callback

I need to open 20 pages parallelly and click on a button then wait for a response after that get the data from a tag. and my code is:
async function getPageData(links) {
return new Promise(async (resolve, reject) => {
try {
const cluster = await Cluster.launch({
concurrency: Cluster.CONCURRENCY_PAGE,
maxConcurrency: 200,
monitor: true,
});
let allData = [];
await cluster.task(async function getData({ page, data: url }) {
await page.goto(url, {
waitUntil: 'networkidle2',
});
const buttonQuery = 'button[role=tab]:first-child';
const buttonElement = await page.waitForSelector(buttonQuery);
await buttonElement.click(buttonElement);
await page.waitForResponse('https://XXX'); // the problem is here
const data = await page.evaluate(getList);
const [oscillators, summary, movingAverage] = data;
allData.push({ oscillators, summary, movingAverage });
});
links.map(async function addQueue(link) {
cluster.queue(link);
});
await cluster.idle();
await cluster.close();
resolve(allData);
} catch (e) {
reject(e);
}
});
but it just work for the first time and ignore the rest of the tasks. but when I remove page.waitForResponse() the all tasks will be run as expected.
How can I make all tasks wait for their response then extract the data?

Jasmine 4: Async function did not complete within 5000ms issue

I have an existing async function:
async doJSONGetRequest(getUrl, accessToken) {
return new Promise(function(resolve, reject) {
const reqHeaders = {
'Content-Type': 'application/json',
'Authorization': `Bearer ${accessToken}`,
};
console.info('url = ' + getUrl);
request.get({
url: getUrl,
headers: reqHeaders,
}, function(err, response) {
if (err) return reject(err);
try {
// console.debug(`response = ${response.body}`);
const parsed = JSON.parse(response.body);
return resolve(parsed);
} catch (err) {
return reject(err);
}
});
});
}
}
I'm trying to test it with Jasmine(v4).
Of course, I don't want this thing to actually make an HTTP request, so I tried rigging up a spy on the 'request' package's 'get' function in the 'beforeAll' section:
describe('RAPIDAPIService', function() {
beforeAll(async function() {
spyOn(request, 'get')
.and
.callFake(async (parameters) => {
if (parameters.url === 'http://localhost/api/getSomething') {
const rsp = {};
rsp.body = 'good stuff';
return rsp;
} else if (parameters.url === 'http://localhost/api/whoops') {
return new Error('401 not found');
} else {
return null;
}
});
});
it('doJSONGetRequest should run successfully', async () => {
expect(api.doJSONGetRequest).toBeDefined();
const res = await api.doJSONGetRequest('http://localhost/api/getSomething', '12345678');
expect(data).toEqual('good stuff');
});
it('doJSONGetRequest should resolve errors properly', async () => {
expect(api.doJSONGetRequest).toBeDefined();
const res = await api.doJSONGetRequest('http://localhost/api/whoops', '12345678');
const expectedError = new Error('401 not found');
expect(res).toEqual(expectedError);
});
Console log statements seem to indicate that I'm actually getting past / returning something from my "await" calls in the "it" tests. But the spies are actually working / detecting that the url's have been called.
(Note that I'm not including here other tests in the same file that do not make asynchronous calls and ARE working... just so you know that there's no problem accessing the actual "api" library and its functions.)
These two tests keep failing with "Error: Timeout - Async function did not complete within 5000ms". And like I said, it seems like they're not returning back to the tests from their calls to the doJSONGetRequest function.
Any thoughts?
Thanks!
I am thinking the issue is the mocking. request.get seems to take two parameters and I am thinking you need to call the 2nd parameter (callback function) once you are done so the resolve can be called.
Try this:
spyOn(request, 'get')
.and
// add callbackFunction as 2nd argument
.callFake((parameters, callbackFunction) => {
if (parameters.url === 'http://localhost/api/getSomething') {
const rsp = {};
rsp.body = 'good stuff';
callbackFunction(null, rsp);
} else if (parameters.url === 'http://localhost/api/whoops') {
callbackFunction({ error: '401 not found' }, {});
} else {
callbackFunction(null, null);
}
});

Returning a value from an asynchronous callback (Node.JS)

So, I have my route which console.logs 'undefined':
router.get("/validate-pin", async (req, res) => {
// restrict when done
try {
const { userId, pin } = req.query;
const isActivePin = await pinsDB.compareActivePin(userId, pin);
console.log(isActivePin)
return res.status(200).json(isActivePin);
} catch (error) {
console.log(error);
res.status(500).json({ error: "db error: ", error });
}
});
I have my compareActivePin method, which logs out the 'res' parameter, but for some reason doesn't return it:
async function compareActivePin(userId, received_pin) {
const active_pin = await db("account_pins").where({ userId, isActive: true });
const pinIsValidated = bcrypt.compareSync(
received_pin,
active_pin[0].account_pin
);
if (pinIsValidated) {
let skLocation = await db("sks").where({ userId }).select("url");
await readKey(skLocation[0].url, (res) => {
// console.log(res);
return res;
});
} else return false;
}
And I have my readKey method, which actually grabs the data I want my compareActivePin to return. This works like a charm.
const readKey = async (key, callback) => {
const aws = require("aws-sdk");
aws.config.update({
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
region: "us-east-2",
});
const s3 = new aws.S3();
const getParams = { Bucket: process.env.SK_BUCKET, Key: `${key}.txt` };
await s3.getObject(getParams, (err, data) => {
if (err) return err;
return callback(data.Body.toString());
});
};
So, just to recap. When I hit my endpoint, I pass in a userId and pin (strings). This calls the compareActivePin method which validates the pin and then, if the pin is valid, it then calls readKey, which grabs the file from S3 and returns the text within the file.
Like I said, I'm able to log it out to the console from within the readKey callback, but when I try to log it out as the returned value from the route, it comes back undefined.
Hoping someone could point me in the right direction.
Thanks...
I ended up answering my own question. I don't think it's possible to get a return value from the callback, so I ended up paring down the call from the database and sending the response from the readKey function using the router response object, like so:
//CompareActivePin Function
async function compareActivePin(userId, received_pin) {
const active_pin = await db("account_pins").where({ userId, isActive: true });
const pinIsValidated = bcrypt.compareSync(
received_pin,
active_pin[0].account_pin
);
return pinIsValidated;
}
//Router Call
router.get("/validate-pin", async (req, res) => {
// restrict when done
try {
const { userId, pin } = req.query;
const isActivePin = await pinsDB.compareActivePin(userId, pin);
if (isActivePin) {
let skLocation = await skDB.findUrl(userId);
readKeyFunc(skLocation[0].url, (result) => {
return res.status(200).json({ confirmed: isActivePin, key: result });
});
} else return res.status(401).json({ confirmed: isActivePin, key: null });
} catch (error) {
res.status(500).json({ error: "db error: ", error });
}
});
This also goes a long way toward keeping my database methods pure and separating my concerns.
Thanks, StackOverflow!

Retry multiple fetch

How do I retry this fetch x times if it fails?
The code is based on this article: https://dmitripavlutin.com/javascript-fetch-async-await/
async function fetchData() {
const [firstResponse, secondResponse] = await Promise.all([
fetch(firstUrl),
fetch(secondUrl),
]);
const first = await firstResponse.json();
const second = await secondResponse.json();
return [first, second];
}
fetchData()
.then(([first, second]) => {
console.log("success");
})
.catch((error) => {
console.log("error");
});
Since the requests are independent of each other, I'd have a utility function that will retry X times and then use that in the Promise.all. I'd also have a utility function for fetching JSON that handles the fetch API footgun where it doesn't check HTTP success (see my blog post here). So something along these lines:
// Fetch JSON
function fetchJSON(...args) {
const response = await fetch(...args);
if (!response.ok) {
throw new Error(`HTTP error ${response.status}`);
}
return response.json();
}
// Fetch JSON with up to `retries` retries
async fetchJSONWithRetry(retries, ...args) {
while (retries > 0) {
try {
const result = await fetchJSON(...args);
return result;
} catch (e) {
if (--retries === 0) {
throw e;
}
}
}
}
// Your `fetchData`
async function fetchData(retries = 5) {
const [first, second] = await Promise.all([
fetchJSONWithRetry(retries, firstUrl),
fetchJSONWithRetry(retries, secondUrl),
]);
return [first, second];
}

Await on an async lambda function gjs

I'm trying to get make the async calls bellow (they are async because of a external API, not my design) to run sequentially, now I managed to have foo be awaited by it's calling function but I'm having trouble awaiting for foo2 because I get the following error on the async line
JS ERROR: SyntaxError: missing ) after argument list
What am I missing?
ps: Also is there a better way to "return" a value from the callback than setting a global variable and accessing it from outside?
foo(nick) {
return new Promise((resolve, reject) async () => {
async_foo(par, [],
(c, res) => {
let par2;
try {
par2 = c.somefun(res);
} catch (e) {
logError(e, `Some error`);
return;
}
let output = await this.foo2(par2);
resolve(output);
});
});
}
foo2(par2) {
return new Promise((resolve, reject) => {
par2.asyncfun(
null, this.callback.bind(this, par2));
});
}
Thank you in advance
I think you're just trying to do too much in one Promise:
async function(nick) {
let res1 = await new Promise((resolve, reject) => {
async_foo(par, [], (c, res) => {
try {
resolve(async_foo_finish(res));
} catch (e) {
reject(e);
}
});
});
return new Promise((resolve, reject) => {
res1.asyncfunc(null, (obj, res) => {
try {
resolve(obj.asyncfun_finish(res));
} catch (e) {
reject(e);
}
});
});
}
foo('something').then(output => {
log('output');
}).catch(e => {
logError(e);
});
It's hard to give good advice, since you're not showing real functions.
One of the main purposes of Promises/async-await is to avoid complicated callback nesting. You should generally break your chain of functions into separate Promises, then await them one after the other.

Resources