I'm trying to call a 3rd party API in my Alexa Skill, and I'm getting a "Session ended with reason: ERROR" in CloudWatch log. The issue appears to be in my NumberIntentHandler or my httpGet function, but I'm not sure where.
UPDATED CODE
-- Handler that's getting fired --
const NumberIntentHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'IntentRequest'
&& handlerInput.requestEnvelope.request.intent.name === 'NumberIntent';
},
handle(handlerInput) {
let slotNum = handlerInput.requestEnvelope.request.intent.slots.number.value;
//var myRequest = parseInt(slotNum);
const myRequest = parseInt(slotNum);
console.log('NumberIntentHandler myRequest: ', myRequest);
var options = `http://numbersapi.com/${myRequest}`;
console.log('NumberIntentHandler options: ', options);
// Use the async function
const myResult = httpGet(options);
console.log("sent : " + options);
console.log("received : " + myResult);
const speechText = myResult;
console.log('speechText: ', speechText); // Print the speechText */
return handlerInput.responseBuilder
.speak(speechText)
.withSimpleCard('Here is your fact: ', speechText)
.getResponse();
},
};
-- Function that's getting called from the Handler --
async function httpGet(options) {
// return new pending promise
console.log(`~~~~~~~~~ httpGet ~~~~~~~~~`);
console.log(`~~~~~${JSON.stringify(options)}~~~~~`);
return new Promise((resolve, reject) => {
const request = http.get(options, (response) => {
// handle http errors
if (response < 200 || response > 299) {
reject(new Error('Failed to load page, status code: ' + response));
}// temporary data holder
const body = [];
// on every content chunk, push it to the data array
response.on('data', (chunk) => body.push(chunk));
// we are done, resolve promise with those joined chunks
response.on('end', () => resolve(body.join('')));
console.log('body: ', body[0]);
});
// handle connection errors of the request
request.on('error', (err) => reject(err));
request.end();
});
}
Updated Code - Eliminated async/await/promise
-- Handler --
const NumberIntentHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'IntentRequest'
&& handlerInput.requestEnvelope.request.intent.name === 'NumberIntent';
},
handle(handlerInput) {
let slotNum = handlerInput.requestEnvelope.request.intent.slots.number.value;
//var myRequest = parseInt(slotNum);
const myRequest = parseInt(slotNum);
console.log('NumberIntentHandler myRequest: ', myRequest);
var options = `http://numbersapi.com/${myRequest}`;
console.log('NumberIntentHandler options: ', options);
// Use the async function
//const myResult = httpGet(options);
const myResult = httpGet(options, res => {
console.log("sent : " + options);
console.log("received : " + myResult);
const speechText = myResult;
console.log('speechText: ', speechText); // Print the speechText */
return handlerInput.responseBuilder
.speak(speechText)
.withSimpleCard('Here is your fact: ', speechText)
.getResponse();
});
},
};
-- Function --
function httpGet(options, cb) {
http.get(options, res => {
console.log(`~~~~~${JSON.stringify(options)}~~~~~`);
// simplified version without error handling
let output = [];
res.on('data', d => output.push(d)); // or concat to a string instead?
res.on('end', () => cb(output));
console.log('output: ', output[0]);
});
}
I believe you will need to call resolve with your response in httpGet.
As a side note (unrelated to your problem) - I can recommend using request-promise, it implements a very nice promise api around http and would simplify your code in this case. (I know I know, async/await are new and fun tools but in this case I would go with "simpler" :) ).
Also, if I remember correctly, the callback for http.get is being invoked with only one argument.
edit, after changes:
you could get rid of the promise and async to simplify your code.
Just a note on async/await - if the awaited expression isn't a promise then it is cast into one automatically. In your current code you either need to use it like a promise (chain a .then() for example) or await it.
Anyways, here is an example that is just using a callback:
function httpGet(options, cb) {
http.get(options, res => {
// simplified version without error handling
let output = [];
res.on('data', d => output.push(d)); // or concat to a string instead?
res.on('end', () => cb(output));
});
}
httpGet(options, res => {
// building the alexa response, all your intent handler code that needs the response from your request
})
Related
I have a TS library using Node v19.1.0. The library has a function that observes streamed server events.
The server provides a /events route streaming 'application/x-ndjson' content which might be an event/ping/... ( sending a ping every x seconds is important to keep the connection alive )
My observe function parses the streamed data and inspects it. If it is a valid event it will pass it to a callback function. The caller also receives an abort function to abort the streaming on demand.
Whenever I run tests locally or via CI I get the following error
Warning: Test "observes events." generated asynchronous activity after the test ended. This activity created the error "AbortError: The operation was aborted." and would have caused the test to fail, but instead triggered an unhandledRejection event.
I tried to minimize the example code using plain JavaScript
const assert = require('assert/strict');
const express = require('express');
const { it } = require('node:test');
it('observes events.', async () => {
const expectedEvent = { type: 'event', payload: { metadata: { type: 'entity-created', commandId: 'commandId' } } };
const api = express();
const server = api
.use(express.json())
.post('/events', (request, response) => {
response.writeHead(200, {
'content-type': 'application/x-ndjson',
});
const line = JSON.stringify(expectedEvent) + '\n';
response.write(line);
})
.listen(3000);
let stopObserving = () => {
throw new Error('should never happen');
};
const actualEventPayload = await new Promise(async resolve => {
stopObserving = await observeEvents(async newEvent => {
resolve(newEvent);
});
});
stopObserving();
server.closeAllConnections();
server.close();
assert.deepEqual(actualEventPayload, expectedEvent.payload);
});
const observeEvents = async function (onReceivedFn) {
const abortController = new AbortController();
const response = await fetch('http://localhost:3000/events', {
method: 'POST',
headers: { 'content-type': 'application/json' },
signal: abortController.signal,
});
if (!response.ok) {
throw new Error('error handling goes here - request failed');
}
Promise.resolve().then(async () => {
if (!response.body) {
throw new Error('error handling goes here - missing response body');
}
for await (const item of parseStream(response.body, abortController)) {
switch (item.type) {
case 'event': {
await onReceivedFn(item.payload);
break;
}
case 'ping':
// Intentionally left blank
break;
case 'error':
throw new Error('error handling goes here - stream failed');
default:
throw new Error('error handling goes here - should never happen');
}
}
});
return () => { abortController.abort(); };
};
const parseLine = function () {
return new TransformStream({
transform(chunk, controller) {
try {
const data = JSON.parse(chunk);
// ... check if this is a valid line...
controller.enqueue(data);
} catch (error) {
controller.error(error);
}
},
});
};
const splitLines = function () {
let buffer = '';
return new TransformStream({
transform(chunk, controller) {
buffer += chunk;
const lines = buffer.split('\n');
for (let i = 0; i < lines.length - 1; i++) {
controller.enqueue(lines[i]);
}
buffer = lines.at(-1) ?? '';
},
flush(controller) {
if (buffer.length > 0) {
controller.enqueue(buffer);
}
},
});
};
const parseStream = async function* (stream, abortController) {
let streamReader;
try {
const pipedStream = stream
.pipeThrough(new TextDecoderStream())
.pipeThrough(splitLines())
.pipeThrough(parseLine());
streamReader = pipedStream.getReader();
while (true) {
const item = await streamReader.read();
if (item.done) {
break;
}
yield item.value;
}
} finally {
await streamReader?.cancel();
abortController.abort();
}
};
Unfortunately, when running node --test, the test does not finish. I have to cancel it manually.
The test breaks with these lines
const actualEventPayload = await new Promise(async resolve => {
stopObserving = await observeEvents(async newEvent => {
resolve(newEvent);
});
});
and I think that's because the Promise never resolves. I thought the stream parsing might have a bug but if you remove all the stream parsing stuff and replace
Promise.resolve().then(async () => {
/* ... */
});
with
Promise.resolve().then(async () => {
await onReceivedFn({ metadata: { type: 'entity-created', commandId: 'commandId' }});
});
it doesn't work neither. Does someone know what's wrong or missing?
The problem here has nothing to do with your promise not resolving since you never even get to that point.
The problem here is that observeEvents is not yet initialized when the test is being run and thus throws a ReferenceError: Cannot access 'observeEvents' before initialization error.
To see that for yourself you can add a simple const it = (name, fn) => fn(); stub to the top of the file and run it without the --test.
There are multiple ways to fix this and the simplest one is to move the test function to the bottom of the file.
If you don't want to do that you can also define the observeEvents function like this: async function observeEvents(onReceivedFn) {...}. This way it will be available immediately.
I am working on a site using Express.js, node.js, Axios, and ejs. I am making REST calls to a Oracle SQL REST services using Axios. I am having trouble working with Promises or Async/Await. I could use some guidance, if possible.
I have a repository layer to interface with the Oracle DB. For example:
dataaccess.js
const axios = require('axios');
exports.IsManufacturerCategory = function (categoryId) {
axios.get(`DB ADDRESS ${categoryId}`)
.then(response => {
console.error('GET IsManufacturerCategory categoryId = ' + categoryId);
console.error('Response = ' + JSON.stringify(response.data));
return (response.data);
})
.catch(rej => {
console.error('ERROR IsManufacturerCategory categoryId = ' + categoryId);
console.error('ERR = \n' + rej.data);
return (rej.data);
});
}
Which is called in my middleware. When I call var isManufacturerCat = exports.IsManufacturerCategory(categoryId); it is undefined. I am attempting to use the data retrieved from the Axios call to return a ejs view to my router, which I can provide if needed.
category.js
var isManufacturerCat = exports.IsManufacturerCategory(categoryId);
if (isManufacturerCat) {
var models = dataaccess.GetCategorySubCategories(categoryId);
return ("manufacturers", {
data: {
Canonical: cononical,
Category: category,
IsAManufacturerCategory: iAManufacturerCat,
Models: models
}
});
}
I am open to any advice in my project structure, usage of Promises, Async/Await, etc.
Thank you in advance.
EDIT
After working with some of the answers given, I have made some progress but I am having issues with layers of async calls. I end up getting into a spot where I need to await a call, but I am in a function that I am not able/do not want to do so (i.e. my router).
indexMiddleware.js
exports.getRedirectURL = async function (fullOrigionalpath) {
if (fullOrigionalpath.split('.').length == 1 || fullOrigionalpath.indexOf(".aspx") != -1) {
if (fullOrigionalpath.indexOf(".aspx") != -1) {
//some string stuff to get url
}
else if (fullOrigionalpath.indexOf("/solutions/") != -1) {
if (!fullOrigionalpath.match("/solutions/$")) {
if (fullOrigionalpath.indexOf("/t-") != -1) {
//some stuff
}
else {
var solPart = fullOrigionalpath.split("/solutions/");
solPart = solPart.filter(function (e) { return e });
if (solPart.length > 0) {
var solParts = solPart[solPart.length - 1].split("/");
solParts = solParts.filter(function (e) { return e });
if (solParts.length == 1) {
waitForRespose = true;
const isASolutionCategory = await dataaccess.isASolutionCategory(solParts[0]); // returns void
if (isASolutionCategory != undefined && isASolutionCategory.length > 0 && isASolutionCategory[0].Count == 1) {
// set redirecturl
}
}
else {
redirecturl = "/solutions/solutiontemplate";
}
}
}
}
}
else if (URL STUFF) {
// finally if none of the above fit into url condition then verify current url with Category URL or product url into database and if that matches then redirect to proper internal URL
if (fullOrigionalpath.lastIndexOf('/') == (fullOrigionalpath.length - 1)) {
fullOrigionalpath = fullOrigionalpath.substring(0, fullOrigionalpath.lastIndexOf('/'));
}
waitForRespose = true;
const originalURL = await exports.getOriginalUrl(fullOrigionalpath); //returns string
redirecturl = originalURL;
return redirecturl;
}
}
if (!waitForRespose) {
return redirecturl;
}
}
exports.getOriginalUrl = async function (friendlyUrl) {
var originalUrl = '';
var urlParts = friendlyUrl.split('/');
urlParts = urlParts.filter(function (e) { return e });
if (urlParts.length > 0) {
var skuID = urlParts[urlParts.length - 1];
const parts = await dataaccess.getFriendlyUrlParts(skuID); //returns void
console.log("Inside GetOriginalUrl (index.js middleware) FriendlyUrlParts: " + parts);//undefined
if (parts != undefined && parts != null && parts.length > 0) {
//some stuff
}
else {
// verify whether it's category URL then return the category local URL
console.log('Getting CategoryLocalUrl');
const categoryLocalUrl = await dataaccess.getCategoryLocalUrl(friendlyUrl); // returns void
console.log('CategoryLocalUrl Gotten ' + JSON.stringify(categoryLocalUrl)); //undefined
if (categoryLocalUrl != undefined && categoryLocalUrl.length > 0) {
//set originalUrl
return originalUrl;
}
}
}
else { return ''; }
}
index.js router
router.use(function (req, res, next) {
//bunch of stuff
index.getRedirectURL(url)
.then(res => {
req.url = res;
})
.catch(error => {
console.error(error);
})
.finally(final => {
next();
});
}
I am getting undefined in my console.logs after the awaits. I'm not really sure what I'm doing I guess.
Let's start with dataaccess.js. Basically, you're exporting a function that's doing async work, but the function isn't async. Most people want to be able to utilize async/await, so rather than have IsManufacturerCategory accept a callback function, it would better to have the function return a promise. The easiest way to do that is to make the function an async function. Async functions return promises which are resolved/rejected more easily than by returning an explicit promise. Here's how that could be rewritten:
const axios = require('axios');
exports.IsManufacturerCategory = async function (categoryId) {
try {
const response = await axios.get(`DB ADDRESS ${categoryId}`);
console.log('GET IsManufacturerCategory categoryId = ' + categoryId);
console.log('Response = ' + JSON.stringify(response.data));
} catch (err) {
console.error('ERROR IsManufacturerCategory categoryId = ' + categoryId);
console.error('ERR = \n' + rej.data);
throw err;
}
}
Note that I'm using console.error only for errors. Because you wanted to log some error related details, I used a try/catch statement. If you didn't care about doing that, the function could be simplified to this:
const axios = require('axios');
exports.IsManufacturerCategory = async function (categoryId) {
const response = await axios.get(`DB ADDRESS ${categoryId}`);
console.log('GET IsManufacturerCategory categoryId = ' + categoryId);
console.log('Response = ' + JSON.stringify(response.data));
}
This is because async functions automatically swallow errors and treat them as rejections - so the promise returned by IsManufacturerCategory would be rejected. Similarly, when an async function returns a value, the promise is resolved with the value returned.
Moving on to category.js... This looks strange because you're accessing IsManufacturerCategory from the exports of that module when I think you mean to access it from the import of the dataaccess module, right?
Inside this function, you should put any async work in an async function so that you can use await with function that return promises. Here's how it could be rewritten:
const dataaccess = require('dataccess.js');
async function validateManufacturerCat(categoryId) {
const isManufacturerCat = await dataaccess.IsManufacturerCategory(categoryId);
if (isManufacturerCat) {
const models = await dataaccess.GetCategorySubCategories(categoryId);
return ({
manufacturers: {
data: {
Canonical: cononical,
Category: category,
IsAManufacturerCategory: iAManufacturerCat,
Models: models
}
}
});
}
}
validateManufacturerCat(categoryId)
.then(res => {
console.log(res);
})
.catch(err => {
console.error(err);
});
A couple notes:
I changed the return value in the if statement to be a single value. You should try to always return a single value when working with promises and async/await (since you can only resolve/return one value).
I see lots of functions starting with capital letters. There's a convention in JavaScript where functions with capital letters are constructor functions (meant to be invoked with the new keyword).
Here is a working example. Note that you need a callback function to get the data when it is available from the promise
//client.js
const axios = require("axios")
exports.IsManufacturerCategory = function (url, callback) {
axios.get(url)
.then(response=>
{
callback(response.data);
})
.catch(error=>{
callback( error);
})
};
//app.js
const client = require('./client');
function process(data) {
console.log(data)
}
client.IsManufacturerCategory("http://localhost:3000/persons",process);
documentation
I am calling an API where I can only fetch 1000 records per request,
I was able to achieve this using recursion.
I am now trying to achieve the same using promises, I am fairly new to Node.js and JavaScript too.
I tried adding the recursion code in an if else block but failed
var requestP = require('request-promise');
const option = {
url: 'rest/api/2/search',
json: true,
qs: {
//jql: "project in (FLAGPS)",
}
}
const callback = (body) => {
// some code
.
.
.//saving records to file
.
//some code
if (totlExtractedRecords < total) {
requestP(option, callback).auth('api-reader', token, true)
.then(callback)
.catch((err) => {
console.log('Error Observed ' + err)
})
}
}
requestP(option).auth('api-reader', token, true)
.then(callback)
.catch((err) => {
console.log('Error Observed ' + err)
})
I want to execute the method using promise and in a synchronous way,
i.e. I want to wait until the records are all exported to a file and continue with my code
I think its better to create your own promise and simply resolve it when your done with your recursion. Here's a simply example just for you to understand the approach
async function myRecursiveLogic(resolveMethod, ctr = 0) {
// This is where you do the logic
await new Promise((res) => setTimeout(res, 1000)); // wait - just for example
ctr++;
console.log('counter:', ctr);
if (ctr === 5) {
resolveMethod(); // Work done, resolve the promise
} else {
await myRecursiveLogic(resolveMethod, ctr); // recursion - continue work
}
}
// Run the method with a single promise
new Promise((res) => myRecursiveLogic(res)).then(r => console.log('done'));
Here's a clean and nice solution using the latest NodeJS features.
The recursive function will continue executing until a specific condition is met (in this example asynchronously getting some data).
const sleep = require('util').promisify(setTimeout)
const recursive = async () => {
await sleep(1000)
const data = await getDataViaPromise() // you can replace this with request-promise
if (!data) {
return recursive() // call the function again
}
return data // job done, return the data
}
The recursive function can be used as follows:
const main = async () => {
const data = await recursive()
// do something here with the data
}
Using your code, I'd refactored it as shown below. I hope it helps.
const requestP = require('request-promise');
const option = {
url: 'rest/api/2/search',
json: true,
qs: {
//jql: "project in (FLAGPS)",
}
};
/*
NOTE: Add async to the function so you can udse await inside the function
*/
const callback = async (body) => {
// some code
//saving records to file
//some code
try {
const result = await requestP(option, callback).auth('api-reader', token, true);
if (totlExtractedRecords < total) {
return callback(result);
}
return result;
} catch (error) {
console.log('Error Observed ' + err);
return error;
}
}
Created this code using feed back from Amir Popovich
const rp = require('Request-Promise')
const fs = require('fs')
const pageSize = 200
const options = {
url: 'https://jira.xyz.com/rest/api/2/search',
json: true,
qs: {
jql: "project in (PROJECT_XYZ)",
maxResults: pageSize,
startAt: 0,
fields: '*all'
},
auth: {
user: 'api-reader',
pass: '<token>',
sendImmediately: true
}
}
const updateCSV = (elment) => {
//fs.writeFileSync('issuedata.json', JSON.stringify(elment.body, undefined, 4))
}
async function getPageinatedData(resolve, reject, ctr = 0) {
var total = 0
await rp(options).then((body) => {
let a = body.issues
console.log(a)
a.forEach(element => {
console.log(element)
//updateCSV(element)
});
total = body.total
}).catch((error) => {
reject(error)
return
})
ctr = ctr + pageSize
options.qs.startAt = ctr
if (ctr >= total) {
resolve();
} else {
await getPageinatedData(resolve, reject, ctr);
}
}
new Promise((resolve, reject) => getPageinatedData(resolve, reject))
.then(() => console.log('DONE'))
.catch((error) => console.log('Error observed - ' + error.name + '\n' + 'Error Code - ' + error.statusCode));
So I have a method, which I want to call multiple times in a loop. This is the function:
function PageSpeedCall(callback) {
var pagespeedCall = `https://www.googleapis.com/pagespeedonline/v4/runPagespeed?url=https://${websites[0]}&strategy=mobile&key=${keys.pageSpeed}`;
// second call
var results = '';
https.get(pagespeedCall, resource => {
resource.setEncoding('utf8');
resource.on('data', data => {
results += data;
});
resource.on('end', () => {
callback(null, results);
});
resource.on('error', err => {
callback(err);
});
});
// callback(null, );
}
As you see this is an async function that calls the PageSpeed API. It then gets the response thanks to the callback and renders it in the view. Now how do I get this to be work in a for/while loop? For example
function PageSpeedCall(websites, i, callback) {
var pagespeedCall = `https://www.googleapis.com/pagespeedonline/v4/runPagespeed?url=https://${websites[i]}&strategy=mobile&key=${keys.pageSpeed}`;
// second call
var results = '';
https.get(pagespeedCall, resource => {
resource.setEncoding('utf8');
resource.on('data', data => {
results += data;
});
resource.on('end', () => {
callback(null, results);
});
resource.on('error', err => {
callback(err);
});
});
// callback(null, );
}
var websites = ['google.com','facebook.com','stackoverflow.com'];
for (let i = 0; i < websites.length; i++) {
PageSpeedCall(websites, i);
}
I want to get a raport for each of these sites. The length of the array will change depending on what the user does.
I am using async.parallel to call the functions like this:
let freeReportCalls = [PageSpeedCall, MozCall, AlexaCall];
async.parallel(freeReportCalls, (err, results) => {
if (err) {
console.log(err);
} else {
res.render('reports/report', {
title: 'Report',
// bw: JSON.parse(results[0]),
ps: JSON.parse(results[0]),
moz: JSON.parse(results[1]),
// pst: results[0],
// mozt: results[1],
// bw: results[1],
al: JSON.parse(results[2]),
user: req.user,
});
}
});
I tried to use promise chaining, but for some reason I cannot put it together in my head. This is my attempt.
return Promise.all([PageSpeedCall,MozCall,AlexaCall]).then(([ps,mz,al]) => {
if (awaiting != null)
var areAwaiting = true;
res.render('admin/', {
title: 'Report',
// bw: JSON.parse(results[0]),
ps: JSON.parse(results[0]),
moz: JSON.parse(results[1]),
// pst: results[0],
// mozt: results[1],
// bw: results[1],
al: JSON.parse(results[2]),
user: req.user,
});
}).catch(e => {
console.error(e)
});
I tried doing this:
return Promise.all([for(let i = 0;i < websites.length;i++){PageSpeedCall(websites, i)}, MozCall, AlexaCall]).
then(([ps, mz, al]) => {
if (awaiting != null)
var areAwaiting = true;
res.render('admin/', {
title: 'Report',
// bw: JSON.parse(results[0]),
ps: JSON.parse(results[0]),
moz: JSON.parse(results[1]),
// pst: results[0],
// mozt: results[1],
// bw: results[1],
al: JSON.parse(results[2]),
user: req.user,
});
}).catch(e => {
console.error(e)
});
But node just said it's stupid.
And this would work if I didn't want to pass the websites and the iterator into the functions. Any idea how to solve this?
To recap. So far the functions work for single websites. I'd like them to work for an array of websites.
I'm basically not sure how to call them, and how to return the responses.
It's much easier if you use fetch and async/await
const fetch = require('node-fetch');
async function PageSpeedCall(website) {
const pagespeedCall = `https://www.googleapis.com/pagespeedonline/v4/runPagespeed?url=https://${website}&strategy=mobile&key=${keys.pageSpeed}`;
const result = await fetch(pagespeeddCall);
return await result.json();
}
async function callAllSites (websites) {
const results = [];
for (const website of websites) {
results.push(await PageSpeedCall(website));
}
return results;
}
callAllSites(['google.com','facebook.com','stackoverflow.com'])
.then(results => console.log(results))
.error(error => console.error(error));
Which is better with a Promise.all
async function callAllSites (websites) {
return await Promise.all(websites.map(website => PageSpeedCall(website));
}
Starting on Node 7.5.0 you can use native async/await:
async function PageSpeedCall(website) {
var pagespeedCall = `https://www.googleapis.com/pagespeedonline/v4/runPagespeed?url=https://${website}&strategy=mobile&key=${keys.pageSpeed}`;
return await promisify(pagespeedCall);
}
async function getResults(){
const websites = ['google.com','facebook.com','stackoverflow.com'];
return websites.map(website => {
try {
return await PageSpeedCall(website);
}
catch (ex) {
// handle exception
}
})
}
Node http "callback" to promise function:
function promisify(url) {
// return new pending promise
return new Promise((resolve, reject) => {
// select http or https module, depending on reqested url
const lib = url.startsWith('https') ? require('https') : require('http');
const request = lib.get(url, (response) => {
// handle http errors
if (response.statusCode < 200 || response.statusCode > 299) {
reject(new Error('Failed to load page, status code: ' + response.statusCode));
}
// temporary data holder
const body = [];
// on every content chunk, push it to the data array
response.on('data', (chunk) => body.push(chunk));
// we are done, resolve promise with those joined chunks
response.on('end', () => resolve(body.join('')));
});
// handle connection errors of the request
request.on('error', (err) => reject(err))
})
}
Make PageSpeedCall a promise and push that promise to an array as many times as you need, e.g. myArray.push(PageSpeedCall(foo)) then myArray.push(PageSpeedCall(foo2)) and so on. Then you Promise.all the array.
If subsequent asynch calls require the result of a prior asynch call, that is what .then is for.
Promise.all()
Promise.all([promise1, promise2, promise3]).then(function(values) {
console.log(values);
});
I'm trying deal with a library that using async functions and am a little lost. I want to call a function that returns a string but am getting tripped up. Here's what I have so far. The ZeroEx library functions all seem to use async /await so my understanding is that I can only call them from another async method. But won't this just cause a chain reaction meaning every method needs to be async? Or am I missing something?
function main() {
var broker = zmq.socket('router');
broker.bindSync('tcp://*:5671');
broker.on('message', function () {
var args = Array.apply(null, arguments)
, identity = args[0]
, message = args[1].toString('utf8');
if(message === 'TopOfBook') {
broker.send([identity, '', getTopOfBook()]);
}
//broker.send([identity, '', 'TEST']);
//console.log('test sent');
})
}
async function getTopOfBook() {
var result: string = 'test getTopOfBook';
const EXCHANGE_ADDRESS = await zeroEx.exchange.getContractAddress();
const wethTokenInfo = await zeroEx.tokenRegistry.getTokenBySymbolIfExistsAsync('WETH');
const zrxTokenInfo = await zeroEx.tokenRegistry.getTokenBySymbolIfExistsAsync('ZRX');
if (wethTokenInfo === undefined || zrxTokenInfo === undefined) {
throw new Error('could not find token info');
}
const WETH_ADDRESS = wethTokenInfo.address;
const ZRX_ADDRESS = zrxTokenInfo.address;
return result;
}
main();
The function getTopOfBook() isn't returning anything back to the main() function so the result is never being sent by the broker. The commented out broker.send() with 'TEST' is working fine however. Thanks for looking.
EDIT:
I tried to make the main method async so I could use await but it's giving an error that I can only use await in an async function. Could the broker.on() call be causing this?
const main = async () => {
try{
var broker = zmq.socket('router');
broker.bindSync('tcp://*:5671');
broker.on('message', function () {
var args = Array.apply(null, arguments)
, identity = args[0]
, message = args[1].toString('utf8');
console.log(message);
if(message === 'TopOfBook') {
>> var test = await getTopOfBook();
console.log('in top of book test');
broker.send([identity, '', test]);
}
//broker.send([identity, '', 'TEST']);
//console.log('test sent');
})
} catch (err) {
console.log(err);
}
}
EDIT 2:
My current working code, thanks everyone that had advice/solutions! I obviously have to fill out the getTopOfBook() function to return an actual result still. If you have more recommendations send them my way. I'm trying to build out a backend that will get data from a geth rpc and send it to a C# GUI front end.
var main = function() {
try{
var broker = zmq.socket('router');
broker.bindSync('tcp://*:5672');
broker.on('message', function () {
var args = Array.apply(null, arguments)
, identity = args[0]
, message = args[1].toString('utf8');
if(message === 'TopOfBook') {
getTopOfBook().then((result) => {
broker.send([identity, '', result])
});
}
})
} catch (err) {
console.log(err);
}
}
async function getTopOfBook() {
var result: string = 'test getTopOfBook';
const EXCHANGE_ADDRESS = await zeroEx.exchange.getContractAddress();
const wethTokenInfo = await zeroEx.tokenRegistry.getTokenBySymbolIfExistsAsync('WETH');
const zrxTokenInfo = await zeroEx.tokenRegistry.getTokenBySymbolIfExistsAsync('ZRX');
if (wethTokenInfo === undefined || zrxTokenInfo === undefined) {
throw new Error('could not find token info');
}
const WETH_ADDRESS = wethTokenInfo.address;
const ZRX_ADDRESS = zrxTokenInfo.address;
return result;
}
main();
The callback function needs to be async
broker.on('message', async function () {
var args = Array.apply(null, arguments)
, identity = args[0]
, message = args[1].toString('utf8');
if(message === 'TopOfBook') {
var test = await getTopOfBook();
broker.send([identity, '', test]);
}
//broker.send([identity, '', 'TEST']);
//console.log('test sent');
})
You are missing the point that async functions are just a way to write the code. Every async function what actually does is generate a promise. That is why you can await any promise and you can interact with any library that uses async without the need of using async functions yourself.
When you call an async function it should return a promise (which happens automatically on async function). A promise is nothing but an object with a then method. Such method accepts a callback, where you can handle the rest of the logic.
function main() {
var broker = zmq.socket('router');
broker.bindSync('tcp://*:5671');
broker.on('message', function () {
var args = Array.apply(null, arguments)
, identity = args[0]
, message = args[1].toString('utf8');
if(message === 'TopOfBook') {
return getTopOfBook().then( result =>
broker.send([identity, '', result])
) // If the broker also returns a promise, you can continue the flow here
.then(()=> console.log('test sent'))
}
})
}
Personally I don't like async await at all because the involve too much magic and make people to forget about the actual nature of promises and asynchronous code.
Also when dealing with promises you should always remember to return any promise you could call/generate so outer code can continue the chain and handle error messages.
Your function getTopOfBook returns a Promise, so you need to use the function then
Call that function as follow:
getTopOfBook().then((result) => {
console.log("Result:" + result);
});
Look at this code snippet
let sleep = (fn) => {
setTimeout(fn, 1000);
};
let getContractAddress = function(cb) {
return new Promise((r) => sleep(() => {
r('getContractAddress')
}));
};
let getTokenBySymbolIfExistsAsync = function(str) {
return new Promise((r) => sleep(() => {
r({
address: 'getTokenBySymbolIfExistsAsync: ' + str
})
}));
};
let WETH_ADDRESS = '';
let ZRX_ADDRESS = '';
let EXCHANGE_ADDRESS = '';
async function getTopOfBook() {
var result = 'test getTopOfBook';
const EXCHANGE_ADDRESS = await getContractAddress();
const wethTokenInfo = await getTokenBySymbolIfExistsAsync('WETH');
const zrxTokenInfo = await getTokenBySymbolIfExistsAsync('ZRX');
if (wethTokenInfo === undefined || zrxTokenInfo === undefined) {
return Promise.reject(new Error('could not find token info'));
}
const WETH_ADDRESS = wethTokenInfo.address;
const ZRX_ADDRESS = zrxTokenInfo.address;
console.log(WETH_ADDRESS);
console.log(ZRX_ADDRESS);
console.log(EXCHANGE_ADDRESS);
return result;
}
var main = function() {
console.log('Waiting response...');
getTopOfBook().then((result) => {
console.log("Result:" + result);
console.log('DONE!');
}).catch((error) => {
console.log(error);
});
};
main();
.as-console-wrapper {
max-height: 100% !important
}
If you want to throw an error use the function Promise.reject()
Along with that call, you need either to pass the reject function or call the catch function.
In this example, we're passing the reject function:
(error) => {
console.log(error);
}
If you don't pass the reject function, you need to call the catch function in order to handle the thrown error.
let sleep = (fn) => {
setTimeout(fn, 1000);
};
let getContractAddress = function(cb) {
return new Promise((r) => sleep(() => {
r('getContractAddress')
}));
};
let getTokenBySymbolIfExistsAsync = function(str) {
return new Promise((r) => sleep(() => {
r()
}));
};
let WETH_ADDRESS = '';
let ZRX_ADDRESS = '';
let EXCHANGE_ADDRESS = '';
async function getTopOfBook() {
var result = 'test getTopOfBook';
const EXCHANGE_ADDRESS = await getContractAddress();
const wethTokenInfo = await getTokenBySymbolIfExistsAsync('WETH');
const zrxTokenInfo = await getTokenBySymbolIfExistsAsync('ZRX');
if (wethTokenInfo === undefined || zrxTokenInfo === undefined) {
return Promise.reject('Could not find token info');
}
const WETH_ADDRESS = wethTokenInfo.address;
const ZRX_ADDRESS = zrxTokenInfo.address;
console.log(WETH_ADDRESS);
console.log(ZRX_ADDRESS);
console.log(EXCHANGE_ADDRESS);
return result;
}
var main = function() {
console.log('Waiting response...');
getTopOfBook().then((result) => {
console.log("Result:" + result);
console.log('DONE!');
}, (error) => {
console.log(error);
}).catch((error) => {
console.log(error); // This line will be called if reject function is missing.
});
};
main();
.as-console-wrapper {
max-height: 100% !important
}
Resource
Promise.prototype.then()
Promise.reject()
Async function
When an async function is called, it returns a Promise. When the async function returns a value, the Promise will be resolved with the returned value. When the async function throws an exception or some value, the Promise will be rejected with the thrown value.