Using Promises with Await/Async Correctly - javascript

I'm having some issues understanding how the Promise functionality works, I have previously used Bluebird but I wanted to try to learn the new await/async standard in order to improve as a programmer. I have used async/await and created promises where I feel appropriate however the functions are still executing out of order.
I'm running this on the latest version of Node with Webpack, I'm not getting any meaningful errors. It runs fine just not as expected. My output when running it is:
Searching the Web for: Test String
Web search Completed!
Promise { <pending> }
Response Handler Completed!
Ideally I'd like it to respond with:
Searching the Web for: Test String
Response Handler Completed
Web search Completed
And then return the output of my response handler.
Can anyone spot my mistake?
const https = require('https');
// Replace the subscriptionKey string value with your valid subscription key.
const subscriptionKey = '<samplekey>';
const host = 'api.cognitive.microsoft.com';
const path = '/bing/v7.0/search';
const response_handler = async (response) => {
return new Promise((resolve, reject) => {
let body = '';
response.on('data', (d) => {
body += d;
resolve(body);
});
response.on('end', () => {
console.log('\nRelevant Headers:\n');
for (const header in response.headers)
// header keys are lower-cased by Node.js
{
if (header.startsWith('bingapis-') || header.startsWith('x-msedge-')) { console.log(`${header}: ${response.headers[header]}`); }
}
body = JSON.stringify(JSON.parse(body), null, ' ');
//console.log('\nJSON Test Response:\n');
//console.log(body);
});
response.on('error', (e) => {
console.log(`Error: ${e.message}`);
});
console.log('Response Handler Completed!');
});
};
const bing_web_search = async (search) => {
return new Promise((resolve, reject) => {
console.log(`Searching the Web for: ${search}`);
const request_params = {
method: 'GET',
hostname: host,
path: `${path}?q=${encodeURIComponent(search)}&$responseFilter=${encodeURIComponent('Webpages')}&count=${50}`,
headers: {
'Ocp-Apim-Subscription-Key': subscriptionKey,
},
};
const req = https.request(request_params, response_handler);
console.log('Web search Completed!');
console.log(req.body);
req.end();
});
};
module.exports = {
search: async (search) => {
if (subscriptionKey.length === 32) {
const result = await bing_web_search(search);
console.log('Search Completed');
} else {
console.log('Invalid Bing Search API subscription key!');
console.log('Please paste yours into the source code.');
}
},
};

A bit late but the following should set you on the way, I made changes to the code. If you have any questions please let me know.
const https = require('https');
// Replace the subscriptionKey string value with your valid subscription key.
const subscriptionKey = '<samplekey>';
const host = 'api.cognitive.microsoft.com';
const path = '/bing/v7.0/search';
const response_handler = (resolve,reject) => (response) => { // no need for async, you return a promise
//this one does not return anything, it's the handler for the response and will resolve
// or reject accordingly
let body = '';
response.on('data', (d) => {
body += d;
//cannot resolve yet, we're not done
// you can resolve on end maybe? I don't know nodejs http
// if end event is called when request fails then end would not
// be the correct way either, better use fetch api
//resolve(body);
});
response.on('end', () => {
console.log('\nRelevant Headers:\n');
for (const header in response.headers)
// header keys are lower-cased by Node.js
{
if (header.startsWith('bingapis-') || header.startsWith('x-msedge-')) { console.log(`${header}: ${response.headers[header]}`); }
}
body = JSON.stringify(JSON.parse(body), null, ' ');
resolve(body);//resolving the promise returned by bing_web_search
//console.log('\nJSON Test Response:\n');
//console.log(body);
});
response.on('error', (e) => {
console.log(`Error: ${e.message}`);
//need to reject with the error
reject(e);
});
console.log('Response Handler Completed!');
};
//no need to specify async, you are not awaiting anything
// you are creating a promise, when using non promise asynchronous
// functions that work with callbacks or event emitting objects
// you need resolve and reject functions so you have to return
// new Promise(
// (resolve,reject)=>somecallbackNeedingFunction((err,result)=>
// err ? reject(err) : resolve(result)
// )
// )
const bing_web_search = (search) => {
return new Promise((resolve, reject) => {
console.log(`Searching the Web for: ${search}`);
const request_params = {
method: 'GET',
hostname: host,
path: `${path}?q=${encodeURIComponent(search)}&$responseFilter=${encodeURIComponent('Webpages')}&count=${50}`,
headers: {
'Ocp-Apim-Subscription-Key': subscriptionKey,
},
};
const req = https.request(
request_params,
response_handler(resolve,reject)//passing this resolve and reject
);
//no, request not completed, we just started
console.log('Web search Completed!');
// console.log(req.body); // nothing to log here
req.end();
});
};
module.exports = {
search: async (search) => {
if (subscriptionKey.length === 32) {
//did not change anything bing_web_search returns a promise
// so you can just await it
const result = await bing_web_search(search);
console.log('Search Completed');
//this will resolve with the results
return result
} else {
console.log('Invalid Bing Search API subscription key!');
console.log('Please paste yours into the source code.');
//the caller of this function can handle the rejection
return Promise.reject('Invalid Bing Search API subscription key!');
}
},
};
[update]
Your comment suggest that you do not call search correctly or handle the promise it returns correctly. You have no control over how long a response takes so in a set of responses the first request may return last. This is why you have Promise.all
const searchObjects = [s1,s2];
const Fail = function(reason){this.reason=reason;};
Promise.all(
searchObjects.map(
searchObject => obj.search(searchObject)
.then(
x=>[x,searchObject]//if resolve just pass result
,err =>new Fail([err,searchObject])//if reject add a Fail object with some detail
)
)
)
.then(
results => {
console.log(
"resolved results:",
results.filter(([r,_])=>(r&&r.constructor)!==Fail)
);
console.log(
"failed results:",
results.filter(([r,_])=>(r&&r.constructor)===Fail)
);
}
)
If you have a lot of searches then maybe you want to throttle the amount of responses withing a certain time period or active connections. Let me know if you need help with that.

Related

Wait for response from request before returning

I am trying to create a function with a GET request that returns a portion of the data from the GET request. However, it keeps returning before the data is retrieved, so I keep getting "undefined". How can I set this up so it actually waits for the data to be set before returning?
let getInfo = async () => {
const request = net.request({
url: URL
})
return new Promise((resolve, reject) => { // Promise being here DOES work
request.on('response', (response) => {
response.on('data', (chunk) => {
//return new Promise((resolve, reject) => { //Promise being here does NOT work
let body = JSON.parse(chunk)
let info = body.data
if (info){
resolve(info);
}
reject();
//})
});
});
request.write('')
request.end()
}).then(data => {
console.log("From then: "+data)
return data
})
}
getInfo().then(data => {
console.log("From outside: "+data)
})
Edit: This is the updated version that still does not work. I am trying to use the native electron method and I don't see why this doesn't work. The "From then:" part displays the info correctly. But when run "From outside:" it prints undefined. Does the issue have anything to do with the response.on being nested inside the request.on?
Solution: As #NidhinDavid showed in his answer, the issue was that the promise was inside the 'response' listener. Moving the 'GET' request from start to finish inside the Promise fixed it to giving the correct output. I have updated my code to reflect that for future individuals.
let getInfo = () => {
let info;
const request = net.request({
url: URL
})
return new Promise((resolve, reject) => {
request.on('response', (response) => {
response.on('data', (chunk) => {
request.write('')
request.end()
let body = JSON.parse(chunk)
info = body.data
if (info) {
resolve(info)
} else {
reject('Something went wrong');
}
});
});
})
}
getInfo()
.then(data => {
// this will be your info object
console.log(data)
})
.catch(err => {
// this will log 'Something went wrong' in case of any error
console.log(err)
})
You need to return inside your, on type event handler. Read more about asynchronous code and synchronous code here
I couldn't find the net module and the one which is included with Nodejs do not have request method. So to get the similar concept of event emiters and promise I am using http module and doing a http request to fetch json and parse it
'use strict'
var https = require('https');
const getInfo = async () => {
// create a new promise chain
// remember it is a chain, if one return is omitted
// then the chain is broken
return new Promise((resolve, reject) => {
var options = {
host: 'support.oneskyapp.com',
path: '/hc/en-us/article_attachments/202761727/example_2.json'
};
// start the request
https.request(options, function (response) {
var str = '';
// data arrives in chunks
// chunks needs to be stitched together before parsing
response.on('data', function (chunk) {
str += chunk;
});
// response body obtained
// resolve (aka return) the result
// or parse it, or do whatever you want with it
response.on('end', function () {
resolve(str)
});
// errors are another event
// listen for errors and reject when they are encountered
response.on('error', function (err) {
reject(err)
})
}).end()
})
}
//*********************************************
// using async await
//*********************************************
// if this is the entry point into app
// then top-level async approach required
(async ()=>{
try{
let data = await getInfo()
console.log("From ASYNC AWAIT ")
console.log(JSON.stringify(JSON.parse(data)))
}
catch (err) {
console.log("operation failed, error: ", err)
}
})();
//************************************************
// using promise chains
//************************************************
getInfo()
.then((data)=>{
console.log("FROM PROMISE CHAIN ")
console.log(JSON.stringify(JSON.parse(data)))
})
.catch((err)=>{
console.log("operation failed, error: ", err)
})
Tyr this, it might works for you,
let info;
const getInfo = async (_url)=>{
const response = await fetch(_url);
const data = await response.json();
info = data;
} ;
const url = "some url";
getInfo(url);
console.log(info);
Async function always returns a promise, so either consume that promise or internally await the data and assign it to some variable.
Check for the valid data required in info by logging it to the console.

Why isn't my async function waiting for the promise to be fulfilled

I am using ldapjs to query users from an ldap server.
If I put all the code just in a single script without using functions, the query works and I get the results I need.
I am now trying to use expressjs to serve a rest endpoint to enable querying of the ldap server, so I moved the ldapjs client.search code into a async function with a promise surrounding the actual search code.
After the promise code, I have a line which exercises the promise using await and stores the results of the promise in a variable. I then return that variable to the calling function which will eventually send the results back as a json-formatted string to the requesting browser.
The problem I am seeing is that the console.log() of the returned results is undefined and appears before the console.log statements inside the promise code. So it looks like the async function is returning before the promise is fulfilled, but I don't see why because in all the examples of promises and async/await I have seen this scenario works correctly.
Below is a sample script without the expressjs part to just make sure everything works correctly.
// script constants:
const ldap = require('ldapjs');
const assert = require('assert');
const ldapServer = "ldap.example.com";
const adSuffix = "dc=example,dc=com"; // test.com
const client = getClient();
const fullName = "*doe*";
var opts = {
scope: "sub",
filter: `(cn=${fullName})`,
attributes: ["displayName", "mail", "title", "manager"]
};
console.log("performing the search");
let ldapUsers = doSearch(client, opts);
console.log("Final Results: " + ldapUsers);
function getClient() {
// Setup the connection to the ldap server
...
return client;
}
async function doSearch(client, searchOptions) {
console.log("Inside doSearch()");
let promise = new Promise((resolve, reject) => {
users = '{"users": [';
client.search(adSuffix, searchOptions, (err, res) => {
if (err) {
console.log(err);
reject(err)
}
res.on('searchEntry', function(entry) {
console.log("Entry: " + users.length);
if (users.length > 11) {
users = users + "," + JSON.stringify(entry.object);
} else {
users = users + JSON.stringify(entry.object);
}
});
res.on('error', function(err) {
console.error("Error: " + err.message);
reject(err)
});
res.on('end', function(result) {
console.log("end:");
client.unbind();
users = users + "]}";
resolve(users)
});
});
});
// resolve the promise:
let result = await promise;
console.log("After promise has resolved.");
console.log(result);
return result
}
The output from the console.log statements is as follows:
Setting up the ldap client.
ldap.createClient succeeded.
performing the search
Inside doSearch()
Final Results: [object Promise]
Entry: 11
end:
After promise has resolved.
{"users": [{"dn":"cn=john_doe"}]}
I did strip out the code which creates the ldapjs client and redacted the company name, but otherwise this is my code.
Any ideas on why the doSearch function is returning before the promise is fulfilled would be greatly appreciated.
As #danh mentioned in a comment, you're not awaiting the response from doSearch. Since doSearch is an async function it will always return a promise, and thus must be awaited.
As a quick and dirty way to do that you could wrap your call in an immediately invoked asynchronous function like so:
// ...
(async () => console.log(await doSearch(client, opts)))();
// ...
For more info you might check out the MDN docs on asynchronous functions
I think there are a few issues in the provided code snippet. As #danh pointed out you need to await the doSearch call. However you may have not done that already since you may not be using an environment with a top async. This likely means you'll want to wrap the call to doSearch in an async function and call that. Assuming you need to await for the search results.
// script constants:
const ldap = require('ldapjs');
const assert = require('assert');
const ldapServer = "ldap.example.com";
const adSuffix = "dc=example,dc=com"; // test.com
const client = getClient();
const fullName = "*doe*";
function getClient() {
// Setup the connection to the ldap server
...
return client;
}
async function doSearch(client, searchOptions) {
console.log("Inside doSearch()");
return new Promise((resolve, reject) => {
users = '{"users": [';
client.search(adSuffix, searchOptions, (err, res) => {
if (err) {
console.log(err);
reject(err)
}
res.on('searchEntry', function(entry) {
console.log("Entry: " + users.length);
if (users.length > 11) {
users = users + "," + JSON.stringify(entry.object);
} else {
users = users + JSON.stringify(entry.object);
}
});
res.on('error', function(err) {
console.error("Error: " + err.message);
reject(err)
});
res.on('end', function(result) {
console.log("end:");
client.unbind();
users = users + "]}";
console.log(result);
resolve(users)
});
});
});
}
const opts = {
scope: "sub",
filter: `(cn=${fullName})`,
attributes: ["displayName", "mail", "title", "manager"]
};
(async function runAsyncSearch () {
console.log("performing the search");
try {
const ldapUsers = await doSearch(client, opts); // Await the async results
console.log("After promise has resolved.");
console.log("Final Results: " + ldapUsers);
} catch (err) {
console.error(err.message);
}
})(); // Execute the function immediately after defining it.

What am i doing wrong with Promise rejections here?

So I've made a bit of reusable code here for node, and I'm applying it via async / await. Albeit I'm sure I am misunderstanding a lot here when working with this... But, I swear, I have one project I'm using this code that it works, and another where it doesn't.
Im using request and request-promise.
UrlRequest: function( opts ) {
return new Promise( (resolve, reject) => {
request( opts,
function(error, request, body) {
if (error)
reject( {error: true, msg: error} );
else
resolve( {body, request} );
});
})
.catch(err => reject( {error: true, msg: err} ));
}
I am fairly sure the .catch() is wrong. But it didn't error out in my 1st project. So i'm trying to figure out the proper way of doing this. The few articles I've looked through is where I came up with this function for usage. I also know if any error actually happens ( this case included ), it will throw a UnhandledPromiseRejectionWarning error. So how is this properly handled?
How I use it:
(async () => {
var result = await Promise.UrlRequest( {
url: "...",
method: "GET",
headers: DefaultHeaders
} );
// do stuff with result...
}) ();
Since you already installed request-promise, you don't need constructing the Promise as you are doing. Simply use the it instead of request then you would have a promise returned. Something similar to this should work:
const request = require('request-promise')
request(opts)
.then((res) => {
// Process res...
})
.catch((err) => {
// Handle error...
});
You can proceed to wrap it in your UrlRequest function and use with async as follows:
UrlRequest: async ( opts ) => {
try {
const response = await request(opts);
return response;
} catch (error) {
// Handle error
}
}
In the case that you want to use then() and catch(), you can do this:
UrlRequest: ( opts ) => {
return request(opts)
.then(response => response)
.catch (error) {
// Handle error
}
}
With request-promise, you don't need to write your own Promise wrapper
// make sure you're using the promise version
const request = require('request-promise')
var opts = {
...
resolveWithFullResponse: true // <--- <--- to get full response, response.body contains the body
};
// if you dont plan to use UrlRequest as constructor, better name is starting with lowercase: urlRequest, some naming convention
UrlRequest: async function( opts ) {
let res;
try {
res = await request(opts);
} catch (e) {
// handle error
throw e
}
return res;
}
Note: async function wraps the return in Promise

Why is my apolloFetch call returning an empty query when called from within a promise.all?

I'm trying to use apolloFetch inside a Promise.all in my Node.js microservice but keep getting an error that the query is empty. The reason for using apolloFetch is to call another micro service and pass it an array of queries. Can someone give me some direction? My code is as follows:
const uri = "dsc.xxx.yyyy.com/abc/def/graphql";
const apolloFetch = CreateApolloFetch({uri});
const QryAllBooks = {
type: new GraphQLList(BookType),
args: {},
resolve() {
return new Promise((resolve, reject) => {
let sql = singleLineString`
select distinct t.bookid,t.bookname,t.country
from books_tbl t
where t.ship_status = 'Not Shipped'
`;
pool.query(sql, (err, results) => {
if (err) {
reject(err);
}
resolve(results);
const str = JSON.stringify(results);
const json = JSON.parse(str);
const promises = [];
for (let p = 0; p < results.length; p++) {
const book_id = json[p].bookid;
const query = `mutation updateShipping
{updateShipping
(id: ${book_id}, input:{
status: "Shipped"
})
{ bookid
bookname }}`;
promises.push(query);
}
//Below is the Promise.all function with the
//apolloFetch that calls another graphql endpoint
//an array of queries
Promise.all(promises.map(p => apolloFetch({p}))).then((result) => {
//this is the problem code^^^^^^^^^^^^^^^^^^^^^
resolve();
console.log("success!");
}).catch((e) => {
FunctionLogError(29, "Error", e);
});
});
});
}
};
module.exports = {
QryAllBooks,
BookType
};
It looks like apolloFetch requires query - you are passing p
change
Promise.all( promises.map(p=>apolloFetch({p})) )
to
Promise.all( promises.map(query=>apolloFetch({query})) )
You also call resolve twice
To resolve all errors or success
const final_results = []
Promise.all(promises.map(query => apolloFetch({
query,
}))).then((result) => {
final_results.push(result)
}).catch((e) => {
final_results.push(e)
}).then(() => {
resolve(final_results)
});
You immediately resolve or rejects once the pool.query() callback starts:
if(err){ reject(err);}resolve(results);
So unless the query fails, you never resolve with the results from the apolloFetch calls, since the promise is already resolved with the pool.query() results. I guess you're missing an else block:
if( err ) {
reject();
}
else {
const promises = ...
}
PS: you can try using node.js' util.promisify() to turn pool.query() into a promise as well so you can just write something resembling: query(...).then(results=>results.map(apolloFetch) instead of ahving to mix callbacks and promises.

Returning one object that's built with nested promises

I'm struggling to wrap my head around a nested promise layout where one one object is returned at the end of it. My current code is as follows:
router
router.get(`/${config.version}/event/:id?`, function (req, res, next) {
var event = new Event(req, res, next);
event.getInfo(req.params.id).then((info) => {
res.send(info);
});
});
function
getInfo(id) {
db.main('events').where('id', id).select()
.then((result) => {
if(result.length > 0) {
var event = result[0];
//regular functions
event.status = this.getStatus(id);
event.content = this.getContent(id);
event.price = this.getPrice(id);
//promise functions
var users = this.getUsers(id);
var hosts = this.getHosts(id);
Promise.all([users, hosts]).then(values => {
event.users = values[0];
event.hosts = values[1];
//return whole event object to router
return event;
})
.catch((err) => {
return {
result: 'error',
error: err
};
});
} else {
return {
result: 'error',
error: "Event does not exist"
};
}
}).catch((e) => {
return {
result: 'error',
error: "Could not retrieve event info"
};
});
}
As you can see, the router initiates a call to get info about an event. The function then does a database call and gets some event data. Thereafter I need to get the users and hosts of the event from a different table, append that info to the event object as well and then return the whole object to the router to be sent to the client.
When I do this I get an error because I'm not returning a promise from the getInfo function, but I'm not sure how or which promise I'm supposed to return.
I'd appreciate some help with this. Thanks
using .then means that you are returning a promise.
function getInfo(id) {
return new Promise(function(resolve, reject) {
resolve('yay!');
})
}
getInfo().then(function(result) { //result = yay! });
to make your code work, simply replace all the returns with resolves, the errors with rejects, and wrap the whole thing with a return new Promise as i did.
getInfo(id) {
return new Promise(function(resolve, reject) {
db.main('events').where('id', id).select()
.then((result) => {
if (result.length > 0) {
var event = result[0];
//regular functions
event.status = this.getStatus(id);
event.content = this.getContent(id);
event.price = this.getPrice(id);
//promise functions
var users = this.getUsers(id);
var hosts = this.getHosts(id);
Promise.all([users, hosts]).then(values => {
event.users = values[0];
event.hosts = values[1];
//return whole event object to router
resolve(event);
})
.catch((err) => {
reject({
result: 'error',
error: err
});
});
} else {
reject({
result: 'error',
error: "Event does not exist"
});
}
}).catch((e) => {
reject({
result: 'error',
error: "Could not retrieve event info"
});
});
});
}
Just wrap your async code in Promise like this:
getInfo(id) {
return new Promise(function(resolve, reject) {
db.main('events').where('id', id).select()
.then((result) => {
//...
resolve(/* result */)
// OR
reject(/* Error */)
})
}
Note: Use resolve and reject instead return
It's a combination of several things, but the main one is that you are never returning anything from getInfo, so your router handler is calling .then on undefined.
Do not call .catch (without throwing inside it) on Promises you intend to return for a caller to consume. This makes it not possible to use .catch, because you recovered the Promise chain into a resolved one.
Whatever you return inside a .then will be merged into the promise chain, so it's not actually a "Promise that resolves with a Promise". Your whole code could be replaced with:
getInfo (id) {
return db.main('events').where('id', id).select()
.then(result => {
if (result.length == 0) {
// you can also just throw your error object thing,
// but standard Error are generally the convention
throw new Error('Event does not exist')
}
const [event] = result
event.status = this.getStatus(id)
event.content = this.getContent(id)
event.price = this.getPrice(id)
return Promise.all([this.getUsers(id), this.getHosts(id)])
.then(([users, hosts]) => {
event.users = users
event.hosts = hosts
// this is the only value that
// this.getInfo(id).then(value => {/* ... */}) will see
return event
}
})
}

Categories

Resources