Function runs after another function that comes after - javascript

I'm currently trying to code a discord bot using node.js. Currently, I have a function that returns a value which a variable is set to. Afterwards, the console prints out the variable. However, the variable is printed out on the console before it gets assigned the value from the function.
The code:
if(commands[0] === "getlevel"){
let region = commands[1] + "1";
let username = commands[2];
var summonerLevel;
console.log(region, username);
API.setRegion(region);
summonerLevel = await API.getSummonerLevel(username);
console.log("HI");
console.log(summonerLevel);
message.channel.send(summonerLevel);
}
});
So what happens is that the console prints about summonerLevel before the API.getSummonerLevel(username) function executes. How would I make it so that the getSummonerLevel() function runs before console.log()?
Code for function:
RiotAPI.prototype.getSummonerLevel = function(_username){
var summonerJSON; var summoner;
var url = baseURL1 + region + baseURL2 + "/lol/summoner/v3/summoners/by-name/" + _username + "?api_key=" + API_KEY;
console.log("Before");
var options = {
uri: url,
simple: false
};
request.get(options)
.then(function(body){
console.log("After");
summonerJSON = body;
summoner = JSON.parse(summonerJSON);
console.log(summoner);
console.log(summoner.summonerLevel);
return summoner.summonerLevel;
})
.catch(function(err){
//should.throw.error.to.console();
})
.catch(function(err){
console.log(err);
});
}
Also, another problem I have is I get an unhandled promise rejection warning and the .catch() that comes after request.get(...) doesn't seem to solve it. How would I fix that was well?
Console log (not sure if this is useful but...):
Before
HI
undefined
(node:13624) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 2): DiscordAPIError: Cannot send an empty message
(node:13624) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
After
{ id: 45932456,
accountId: 206692908,
name: 'fire263',
profileIconId: 911,
revisionDate: 1511729881000,
summonerLevel: 36 }
36

You have some issues on your code.
Code for function:
RiotAPI.prototype.getSummonerLevel = function(_username){
var summonerJSON; var summoner;
var url = baseURL1 + region + baseURL2 + "/lol/summoner/v3/summoners/by-name/" + _username + "?api_key=" + API_KEY;
console.log("Before");
var options = {
uri: url,
simple: false
};
return request.get(options) // you need to return the promise
.then(function(body){
console.log("After");
summonerJSON = body;
summoner = JSON.parse(summonerJSON);
console.log(summoner);
console.log(summoner.summonerLevel);
return summoner.summonerLevel;
})
.catch(function(err){ // you should only use `.catch` once
console.log(err);
});
}
As jaromanda x mentioned, you need to return the request.get call which in itself is a promise.
But also you shouldn't use multiple .catch calls because if you do it and you do not handle it correctly (read: continue the rejection chain) it won't go to the second .catch call that you had.
You can read more here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch
More specifically this part: "The Promise returned by catch() is rejected if onRejected throws an error or returns a Promise which is itself rejected; otherwise, it is resolved."
Hopefully this will give you enough information to progress on your issue.

Your getSummonerLevel does not return a promise, which is necessary to be awaitable. The easiest way you can achieve that is to make it async as well:
RiotAPI.prototype.getSummonerLevel = async function(_username) {
var url = baseURL1 + region + baseURL2 + "/lol/summoner/v3/summoners/by-name/" + _username + "?api_key=" + API_KEY;
console.log("Before");
var options = {
uri: url,
simple: false
};
try {
var body = await request.get(options);
console.log("After");
var summonerJSON = body;
var summoner = JSON.parse(summonerJSON);
console.log(summoner);
console.log(summoner.summonerLevel);
return summoner.summonerLevel;
} catch (err) {
//should.throw.error.to.console();
console.log(err);
}
};

Related

How to cancel a promise with $q in angular js

I have a service below. I will call this service every time when I open a model and when I close the model and then open another one the previous values are getting reflected and in this case I want to cancel the promise every time I close the model.
I have tried the following code,
Model closing.js
$scope.closeButton = function() {
DetailDataSvc.storeDefer().resolve()
}
My Service, (DetailDataSvc)
self.storeDefer = function() {
return self.deferReturn;
};
self.getDetailReportData = function(postData, functionName) {
var promises = {};
var d = $q.defer(),
metricDataType;
self.deferReturn = $q.defer();
promises = {
detailReport: metricDataType,
recommendedMetrics: DataSvc.getData(_logPrefix + functionName, recommendedMetricUrl),
metricInfo: DataSvc.getData(_logPrefix + functionName, metricInfoUrl)
};
$q.all(promises).then(function(res) {
$log.debug(_logPrefix + 'getDetailReportData(). Called from %s. $q.all Response (raw): ', functionName, res);
else {
if (response && !_.isEmpty(_.get(response, 'largeCard.chartData.dataValues.rows')) && response.overlayEnabled) {
self.getMetricOverLay(pdata, functionName).then(function(overlayData) {
response.largeCard.chartData.overlay = overlayData;
d.resolve(response);
}, function(msg, code) {
d.reject(msg);
$log.error(_logPrefix + 'getDetailReportData(). Error code: %s. Error: ', code, msg);
});
} else {
d.resolve(response);
}
}
}, function(msg, code) {
d.reject(msg);
$log.error(_logPrefix + 'getDetailReportData(). Error code: %s. Error: ', code, msg);
});
return d.promise;
};
Can anyone please help me whether the process I followed is the right one.
What you have attempted could be made to work but it's best fixed by racing the promise returned by $q.all() against a rejectable Deferred (ie. a Deferred, of which a reference is kept to its reject method), thus avoiding the deferred anti-pattern.
self.getDetailReportData = function(postData, functionName) {
var metricDataType = ......; // ???
var d = $q.defer();
// cancel previous
if(self.cancelDetailReport) {
self.cancelDetailReport(new Error('previous getDetailReportData() cancelled'));
}
// keep a reference to the deferred's reject method for next time round.
self.cancelDetailReport = d.reject;
var promises = {
'detailReport': metricDataType,
'recommendedMetrics': DataSvc.getData(_logPrefix + functionName, recommendedMetricUrl),
'metricInfo': DataSvc.getData(_logPrefix + functionName, metricInfoUrl)
};
// Race aggregated `promises` against `d.promise`, thus providing the required cancellation effect.
return $q.race([$q.all(promises), d.promise])
.then(function(response) {
// arrive here only if all promises resolve and d.reject() has not been called.
$log.debug(_logPrefix + 'getDetailReportData(). Called from %s. $q.all Response (raw): ', functionName, response);
if (response && !_.isEmpty(_.get(response, 'largeCard.chartData.dataValues.rows')) && response.overlayEnabled) {
return self.getMetricOverLay(pdata, functionName)
.then(function(overlayData) {
response.largeCard.chartData.overlay = overlayData;
return response;
});
} else {
return response;
}
})
.catch(function(msg, code) { // signature?
// all error cases including cancellation end up here.
var message = _logPrefix + `getDetailReportData(). Error: (${code}): ${msg}`; // or similar
$log.error(message);
throw new Error(message); // see https://stackoverflow.com/a/42250798/3478010
});
};
Notes:
$q.race() is transparent to whichever promise wins the race, and opaque to the other. So, if the d is rejected before the promise returned by $q.all() settles, then d will win out; response handling will not happen and d's rejection will fall through to the .catch() clause. Alternatively, if the promise returned by $q.all(promises) wins out then flow will follow that promise's success path (ie response handling) or possibly its error path (which will drop through to the .catch() clause).
Not too sure about the signature of the .catch() callback. You would normally expect it to accept a single error argument.
Assign already created deferred.
Try and change this line:
self.deferReturn = $q.defer();
self.deferReturn = d;

Error: "ReferenceError: promise is not defined" adding promise to my code

I have 3 web service calls being made from Node.js. 2 are ReST, 1 is SOAP. All are wrapped in Promises.
I've gotten my ReST requests to return the promises correctly and these are accessible in the Promise.all block but when I add my SOAP request, I get a message saying Promise is not defined.
I'm using node v8.2.1. I've tried request and request-promise but the same thing happens. My code looks like this - anything I'm obviously doing wrong?
const locationRequest = require('request');
var soapPromise = new Promise(function(resolve, reject) {
locationRequest(options1, function(error, response, output) {
if (error) {
console.info("soap error: " + error);
reject(error);
}
else {
console.info("soap success: " + response);
resolve(response);
}
});
return promise;
});
Promise.all([restPromise, photoPromise, soapPromise]) //addition of soapPromise causes the issue
.then(function([restResult, photoResult, soapResult]) {
//respond to client
console.info("Resource: " + restResult.name);
console.info("Photo Path: " + photoResult);
console.info("Soap: " + soapResult);
})
.catch(function(error) {
console.info("promise all error: " + error);
res.send('done');
//catch an error generated from either request
})
Adding the soapPromise stuff gives me:
ReferenceError: promise is not defined
Remove the return promise; line. You're not expected to return anything out of the Promise executor (the callback you give new Promise), and it doesn't create a promise variable. So promise is an undefined identifier at that point, hence the ReferenceError.

Using Bluebird Promises in Node.JS

In my application, I am trying to read a file and then write the contents of the file to another file.
I have used bluebird promises to do this. I need help to confirm that my understanding on using promises is right in my implementation.
The questions that I have is,
In my example, I am first reading the file, then once the file read the file, I am writing the contents into another file in my next '.then' block. Once the contents are written into a file I need log a message. I have included that in the second '.then' block. I need to know whether my understanding is correct on promises. will the second '.then' block works as the callback function for writefile statement?
I need to write more meaningful log messages. Different error messages if an error occurs while reading the file and writing the file. How can I do this with catch blocks?
Thanks in advance!
The code example is below.
var Promise = require("bluebird");
var fs = Promise.promisifyAll(require("fs"));
var logger = require("./logger.js")
var projectJSON = require("../project.json");
var harPath = projectJSON.project.harfile.location;
var harFileNames = projectJSON.project.transactions.transactionsName;
var harFilePath;
var harFiles = {};
var writeFilePath = "../temp/"
harFileNames.forEach(function(harFileName){
harFilePath = harPath + "/" + harFileName + ".har";
fs.readFileAsync(harFilePath, "utf-8")
.then(function(data){
fs.writeFile(writeFilePath + harFileName + ".json", data);
test = data;
})
.then(function(){
console.log("data written successfully: ");
})
.catch(function(err){
logger.error("error", "Error reading har files from location!");
});
});
If you want to capture each error separately, then you can put an error handler immediately after each operation so you can directly capture that particular error.
Then, to propagate the error, you can rethrow the error value.
Then, you want to switch to fs.writeFileAsync() so everything is async and is using promises.
Then, you need to return the promise from fs.writeFileAsync().
var Promise = require("bluebird");
var fs = Promise.promisifyAll(require("fs"));
var logger = require("./logger.js")
var projectJSON = require("../project.json");
var harPath = projectJSON.project.harfile.location;
var harFileNames = projectJSON.project.transactions.transactionsName;
var harFilePath;
var harFiles = {};
var writeFilePath = "../temp/"
harFileNames.forEach(function(harFileName){
harFilePath = harPath + "/" + harFileName + ".har";
fs.readFileAsync(harFilePath, "utf-8")
.then(function(data){
harFiles[JSON.parse(data).log.pages[0].id] = JSON.parse(data);
return data;
}).catch(err) {
logger.error("error", "Error reading har files from location!");
throw err; // propagate error
}).then(function(data){
return fs.writeFile(writeFilePath + harFileName + ".json", data).catch(function(err) {
logger.error("error", "Error writing to harFile!");
throw err; // propagate error
});
}).then(function(){
console.log("data written successfully: ");
}).catch(function(err){
// either one of the errors
});
});
Keep in mind that when you have a .catch() handler, the error is considered "handled" and the resulting promise becomes fulfilled, not rejected. So, if you want to capture the error in a particular spot (so you know exactly where it came from), but you want the resulting promise to remain rejected, then you can either return a rejected promise or rethrow the same error.
Per your additional question about how to return harFiles, you will need a surrounding promise that gets resolved with harFiles when everything is done. Bluebird's Promise.map() is useful for that as it will both do the iteration for you and return a master promise. Here's how that part of the code could look:
function someFunc() {
var harPath = projectJSON.project.harfile.location;
var harFileNames = projectJSON.project.transactions.transactionsName;
var harFilePath;
var harFiles = {};
var writeFilePath = "../temp/"
return Promise.map(harFileNames, function(harFileName) {
harFilePath = harPath + "/" + harFileName + ".har";
return fs.readFileAsync(harFilePath, "utf-8")
.then(function(data){
harFiles[JSON.parse(data).log.pages[0].id] = JSON.parse(data);
return data;
}, function(err) {
logger.error("error", "Error reading har files from location!");
throw err; // propagate error
}).then(function(data){
return fs.writeFile(writeFilePath + harFileName + ".json", data).catch(function(err) {
logger.error("error", "Error writing to harFile!");
throw err; // propagate error
});
}).then(function(){
console.log("data written successfully: ");
}).catch(function(err){
// either one of the errors
});
}).then(function() {
// all results are in harFiles array here
return harFiles;
});
}
someFunc().then(function(hFiles) {
// hFiles should be your files object
});

Bluebird Promise Chains: 'Catch' with Result

In order to make this question as useful to as many people as possible, I will exclude my specific implementation details beyond that fact that I am using the Bluebird promise library with Node + Express below.
So, let's say that I have the following chain (where P returns a promise, and res is the Express HTTP response object):
P().then(function(){
// do nothing if all went well (for now)
// we only care if there is an error
}).catch(function(error){
res.status(500).send("An error occurred");
}).then(function(){
return P();
}).then(function(pVal1){
return [pVal1, P()];
}) // TODO: catch an error from P() here and log pVal1
.spread(function(pVal1, pVal2){
if(pVal1 === pVal2) {
console.log("Success!");
} else {
console.log("Failure");
}
});
Where I have placed the TODO comment above is where I would like to catch an error that might occur from my call to P. If I do catch an error, I would like to log pVal1 and then send a 500 error, as is done in the first catch. However, I am not sure if this is possible with how I am structuring my chain.
I believe that I need to do some "branching," but I do not think that I understand this concept well enough to stop the asynchronous nature of JavaScript from getting the best of me! As such, any help is thoroughly appreciated.
Don't forget to catch errors in the end of the chain. That's also the place to send the response.
Catching errors in the middle of a chain is for intermittent error handling; the chain continues to run, so don't send a response just yet.
Here is something to try it out:
// example middleware
function handle(req, res, next) {
log("----------------");
return async("p1", "foo").then(function (pVal1) {
return pVal1;
}).then(function (pVal1) {
var p2a = async("p2a", "bar"),
p2b = async("p2a", "bar").catch(function (error) {
log("Logging: " + error + " (pVal1 " + pVal1 + ")");
});
return [p2a, p2b];
}).spread(function (pVal1, pVal2) {
if (pVal1 === pVal2) {
res.send("Success!");
} else {
res.send("Failure");
}
}).catch(function (error) {
res.status(500).send("An error occurred");
log("Logging: " + error);
});
}
// ---------------------------------------------------------------------
// mockup response object
var res = {
status: function (code) {
log("Sending status: " + code);
return this;
},
send: function () {
log("Sending response: " + [].join.call(arguments, " "));
return this;
}
};
// mockup promise generator
function async(name, value) {
return new P(function (resolve, reject) {
if ( confirm("let " + name + " succeed?") ) {
log(name + " succeeds...");
resolve(value);
} else {
log(name + " fails...");
reject(name + " has failed");
}
});
}
function log() {
var msg = document.createElement("DIV");
msg.textContent = [].join.call(arguments, " ");
document.getElementById("log").appendChild(msg)
document.body.scrollTop = document.body.scrollHeight;
}
button {
position: fixed;
top: 5px;
}
<script src="http://cdnjs.cloudflare.com/ajax/libs/bluebird/2.9.33/bluebird.min.js"></script>
<button onclick="handle(null, res, null)">Go</button>
<div id="log"></div>
This is possible if you use the explicit Promise.all instead of returning an array into .spread.
}).then(function(pVal1){
// this becomes a `Promise.all` - the aggregation is explicit
var all = Promise.all([pVal1, P()]);
all.catch(function(e){ // "branching", we both return and `catch` the promise
console.log("Error, pVal1 is", pVal1);
});
return all; // return it
}).spread(function(pVal1, pVal2){
// ....
});

Unhandled rejection in Bluebird

I have the following code. It works fine when f2 throws no error.
If there is an error, it generates an Unhandled rejection Error.
What's the proper way to rewrite the code to avoid Unhandled rejection Error and propagate it correctly to catch in f1?
let Bluebird = require('bluebird'),
mkdirp = Bluebird.promisify(require('mkdirp')),
request = Bluebird.promisify(require('request')),
writeFile = Bluebird.promisify(require('fs').writeFile);
function f1() {
.........
f2(path, fileName, options).then(.....).catch(....);
}
function f2(path, fileName, options) {
p = mkdirp(path).then(request(options).then(res => {
if (res[0].statusCode === 200) {
writeFile(fileName, res[0].body);
return res[0].body;
} else {
throw new Error(res[0].statusCode + ': ' + res[0].body);
}
}));
return p;
}
The problem is that you are passing a promise into .then() in f2. .then() will ignore anything that is not a function, so all that f2 is really returning is a promise for mkdirp(this.path) and that's a big bug for a few reasons. If an error is thrown in request(options)'s then handler, then there will be nothing to handle it.
Also, you are not doing anything to handle a possible error from writeFile. If you call writeFile, you either need to return a promise chain that includes it, or add logic to handle it within f2.
Since it looks like you can run mkdirp() and request() in parallel here, but you are not using the result of mkdirp() I would say this is the way to go:
function f2(path, fileName, options) {
var p = mkdirp(path).return(request(options)).then(res => {
if (res[0].statusCode === 200) {
return writeFile(fileName, res[0].body)
.return(res[0].body);
} else {
throw new Error(res[0].statusCode + ': ' + res[0].body);
}
});
return p;
}

Categories

Resources