I am confused by this async behavior.
When token is false, refreshToken() function runs but the createTokenFile() doesn't wait for it to finish.
Shouldn't var tokenDate = new Date(token.expires); wait after callApiToken().then(function() {refreshToken();}) to finish before executing?
function createTokenFile() {
console.log("No token-file.json file found. " .red +
"Please complete for a new one." .red);
return callApiToken().then(function() {
refreshToken();
});
}
function checkExpiredToken() {
return new Promise(function(resolve, reject) {
if (!token) {
refreshToken();
}
var tokenDate = new Date(token.expires);
var utc = new Date().toUTCString();
var now = new Date(utc);
}
function refreshToken() {
try {
var tokenFile = path.join(__dirname, 'token-file.json');
console.log(tokenFile);
return token = JSON.parse(fs.readFileSync(tokenFile, {encoding: 'utf-8'}));
} catch (err) {
if (err.code !== 'ENOENT') {
throw err;
} else {
return createTokenFile();
}
}
}
UPDATED with refreshToken()
Shouldn't var tokenDate = new Date(token.expires); wait after callApiToken().then(function() {refreshToken();}) to finish before executing?
No - it's not in a .then() callback that would wait for the promise to resolve. It only waits until the promise is created - but the promise resolution (that you call "finish") is asynchronous. Notice that promises are not magic, they're just callbacks.
To fix your code,
in createTokenFile you need to return the refreshToken() from the then callback
checkExpiredToken should not use the Promise constructor
refreshToken should always return a promise
there's no reason why refreshToken would read the file synchronously
you shouldn't cache the token as a global variable containing the value
function createTokenFile() {
console.log("No token-file.json file found. " +
"Please complete for a new one.");
return callApiToken();
}
function checkExpiredToken() {
return (tokenPromise || refreshToken())
.then(function(token) {
var tokenDate = new Date(token.expires);
var utc = new Date().toUTCString();
var now = new Date();
});
}
function refreshToken() {
var tokenFile = path.join(__dirname, 'token-file.json');
console.log(tokenFile);
return tokenPromise = readFileAsync(tokenFile, {encoding: 'utf-8'}))
.then(JSON.parse)
.catch(function(err) {
if (err.code !== 'ENOENT') {
throw err;
} else {
return createTokenFile().then(refreshToken);
}
});
}
(where readFileAsync is a promisified version of fs.readFile)
Promise do not de-synchronize code. An function that is asynchronous will always be so. Therefore if refreshToken is asynchronous then it's use above will not wait for it to complete before moving on.
Your code sample leaves too much to the imagination (not to mention syntactically incorrect) so a better answer is not available. Perhaps try recreating the situation in jsbin.com or jsfiddle.net.
Related
I am recursively calling a function which returns a Promise, but i am noticing that the next then is called even if i am rejecting with error. Below is the relevant part of the code.
const applyFilters = (counter) => {
return new Promise((resolve, reject) => {
let filter = filters[counter];
if(filter) {
applyStep(filter).then(promiseTimeout(filter.delay), function(err) {
console.log('reject with error');
reject(err);
}).then(function(res) {
console.log('still executing after reject');
resolve(applyFilters(++counter).catch(function(err) {
reject(err);
}));
});
} else {
resolve(true);
}
});
};
const applyStep = (step) => {
if(step.step_type == 'filter') {
return worksheet.applyFilterAsync(step.name, values, 'replace');
} else if(step.step_type == 'parameter') {
return workbook.changeParameterValueAsync(`${step.name}`, value);
} else {
return Promise.resolve(true);
}
};
I am seeing on console
reject with error
still executing after reject
Is this the expected behaviour, may be I am missing something. Any help in understating this further will be really great. Thanks.
You are passing the second callback to then, which handles the error (in this case by rejecting the outer promise), and then fulfills the promise returned by the then() call with the callback return value (undefined). The next then() in the chain will be executed once that promise is fulfilled.
I could tell you how to work around this problem by using a different then/catch structure, but really you need to avoid the Promise constructor antipattern here!
function applyFilters(counter) {
if (counter >= filter.length)
return Promise.resolve(true);
const filter = filters[counter];
return applyStep(filter)
.then(promiseTimeout(filter.delay))
.then(res => applyFilters(++counter));
}
Hopefully someone can point me to the right direction. I read up on waiting for functions to complete before continuing and I resolved myself to using await/async but I am just stuck now.
I tried to get the Async/Await process to work, tried to inject the await in various locations, with adjusting the functions to be async, but i can not get the PSA_Resultbody to return to the original request. Any pointers would be appreciated.
Thank you,
CE
PSA_Resultbody = ProcessPSAAPI(xmlpackage, PSA_Action);
console.log("3 - Returned data:" + PSA_Resultbody);
calls the below:
async function ProcessPSAAPI(xmlpackage, PSA_Action) { //psa action is part of the options
var options = {...};
var req = https.request(options, function (res) {
var chunks = [];
res.on("data", function (chunk) {
chunks.push(chunk);
});
res.on("end", function (chunk) {
var body = Buffer.concat(chunks);
console.log('0 - Start '+body.toString());
if(res.statusCode != 200) {
PSA_Resultcode = "Error: " +res.statusCode +" - "+ res.statusMessage;
} else {
PSA_Resultcode = "Success: " +res.statusCode +" - "+ res.statusMessage;
PSA_Resultbody = ParseResults(body.toString()); //parse the results for later use --SCRIPT NEEDS TO WAIT FOR RESULTBODY TO COMPLETE
console.log("1 -PSA_Resultbody as part of RES = "+PSA_Resultbody);
}
});
res.on("error", function (error) {
console.error(error);
PSA_Resultcode = res.statusCode +" - "+ res.statusMessage;
});
});
console.log('2 -RESULT BODY BEFORE SENDING BACK TO INITIATING FUNCTION: '+PSA_Resultbody);
req.write(xmlpackage);
req.end();
return PSA_Resultbody;
Based on the above, my console log order is: 3,2,0,1 in stead of 0,1,2,3.
0 and 1 will have the correct data, so the API Call does work, but 2 will be "undefined" and should have the same data that is in 1.
There's no way to await an event emitter, so using async in this case isn't going to be useful. You cannot "return" from inside an event either.
The solution here is to return a new custom promise and to use resolve() inside of the "end" event of your emitter.
It will look something like this:
function ProcessPSAAPI(xmlpackage, PSA_Action) {
return new Promise( (resolve, reject) => {
// other code
res.on("end", function (chunk) {
// other code
resolve(PSA_Resultbody);
});
res.on("error", function (error) {
// other code
reject(error);
});
});
}
Here's a quick tutorial on creating your own promises, which I've written to simplify comprehension of the subject (official docs are somewhat dry and complex imho).
I did not change your code. I just put the appropriate promise structure in to get you started. This should really be a lesson in promises. async await is a shorthand promise structure. A Promise is one way you wait on code. It can be thought of as an array of callbacks that will be executed when the Promise is resolved.
A simple promise works like this:
const myPromise = new Promise(function(resolve, reject) {
/* Your logic goes in here. It can be anything.
* But the important part to remember is that when you have success, resolve it.
* When you have a failure, reject it.
*/
someCallBackPattern(function(error, data) {
if(error) {
reject(error);
} else {
resolve(data);
}
});
});
// To get the data out you use 'then', and 'catch'. then has two arguments.
myPromise.then(function(data) {
// The first argument is the result from resolve.
}, function(err) {
// The second argument is the result from reject.
}).catch((err) => {
// you can also get to the error from the catch callback
});
This is kinda messy and complex. So there is async await.
async function() {
try {
const result = await myFunctionThatReturnsAPromise();
// result is the resolved data
} catch (err) {
// err is the rejected Error
}
}
function myFunctionThatReturnsAPromise() {
return new Promise((resolve, reject) => {
// your code
})
}
And thats how it works.
async function someFunction () { // You can not wait on results unless you are in an await function
PSA_Resultbody = await ProcessPSAAPI(xmlpackage, PSA_Action); // await on your results.
console.log("3 - Returned data:" + PSA_Resultbody);
}
function ProcessPSAAPI(xmlpackage, PSA_Action) { // This does not need to be async. Unless you are awaiting in it.
return new Promise((resolve, reject) => { // async await is a shorthand promise structure. Although you do not need to use promises. It really helps to get the structure correct.
var options = {...};
var req = https.request(options, function (res) {
var chunks = [];
res.on("data", function (chunk) {
chunks.push(chunk);
});
res.on("end", function (chunk) {
var body = Buffer.concat(chunks);
console.log('0 - Start '+body.toString());
if(res.statusCode != 200) {
PSA_Resultcode = "Error: " +res.statusCode +" - "+ res.statusMessage;
reject(new Error(PSA_Resultcode)); // Reject you errors
} else {
PSA_Resultcode = "Success: " +res.statusCode +" - "+ res.statusMessage;
PSA_Resultbody = ParseResults(body.toString()); //parse the results for later use --SCRIPT NEEDS TO WAIT FOR RESULTBODY TO COMPLETE
console.log("1 -PSA_Resultbody as part of RES = "+PSA_Resultbody);
resolve(PSA_Resultbody); // Resolve your result
}
});
res.on("error", function (error) {
console.error(error);
PSA_Resultcode = res.statusCode +" - "+ res.statusMessage;
reject(new Error(PSA_Resultcode)); // Reject you errors
});
});
console.log('2 -RESULT BODY BEFORE SENDING BACK TO INITIATING FUNCTION: '+PSA_Resultbody);
req.write(xmlpackage);
req.end();
})
}
I'm trying to wrap http.get with a Promise. Here's what I've got:
import Http from 'http';
export function get(url) {
let req;
return new Promise((resolve, reject) => {
req = Http.get(url, async res => {
if(res.statusCode !== 200) {
return reject(new Error(`Request failed, got status ${res.statusCode}`));
}
let contentLengthStr = res.headers['content-length'];
if(contentLengthStr) {
let contentLength = parseInt(contentLengthStr, 10);
if(contentLength >= 0 && contentLength <= 2*1024**3) {
let buf = Buffer.allocUnsafe(contentLength);
let offset = 0;
res.on('data', chunk => {
if(chunk.length + offset > contentLength) {
return reject(new Error(`Received too much data, expected ${contentLength} bytes`));
}
chunk.copy(buf, offset);
offset += chunk.length;
});
res.on('end', () => {
if(offset === contentLength) {
resolve(buf);
} else {
return reject(new Error(`Expected ${contentLength} bytes, received ${offset}`));
}
})
} else {
return reject(new Error(`Bad Content-Length header: ${contentLengthStr}`));
}
} else {
return reject(new Error(`Missing Content-Length header not supported`));
}
});
}).catch(err => {
req.abort();
throw err;
})
}
It seems to work OK, but it feels a bit clunky.
Firstly, async/await appear to be of no help here. I can't throw nor return inside of res.on('end'. Returning just returns out of the end callback, but there's no real way for me to break out of the Http.get(url, res => { function from inside there. throwing doesn't "bubble up" to the Promise I created either because the data/end events don't fire synchronously. I have to call reject manually.
The part that really bothers me though is that if the server sends me more data than they said they were going to send me via the Content-Length header, I want to abort the request and stop listening for events. To do that I've rejected the Promise, and then immediately catch it so that I can abort the request, and then I rethrow the error so that the caller can handler it. To do this I have to declare the req variable above the Promise, and then initialise it inside the Promise, so that I can access it after the Promise.
The flow of everything just feels really clunky. Is there a nicer way to write this? (Assume I have available all ES6/ES2017+/next features)
Here is how I would approach the problem:
"promisify' Http.get, meaning converting the API to use promises.
Separate the stream logic into its own separate promise.
Instrument the error logic in a clear flat async function.
This looks something like the following:
import Http from 'http';
// promisify Http.get
const http = (url, abort) => new Promise((resolve, reject) => {
let req = Http.get(url, res => {
if(res.statusCode !== 200) reject(new Error(`Request failed, got status ${res.statusCode}`));
else resolve(res);
});
abort(() => req.abort()); // allow aborting via passed in function
});
export async function get(url) {
let abort = null;
let res = await http(url, (onabort) => { abort = onabort; });
let contentLengthStr = res.headers['content-length'];
if(!contentLengthStr) new Error(`Missing Content-Length header not supported`);
if(contentLengthStr < 0 || contentLengthStr > (2*1024**3 +1)) {
throw new Error(`Bad Content-Length header: ${contentLengthStr}`);
}
try {
return await read(res, contentLength);
} catch (e) {
abort();
throw e;
}
}
function read(res, contentLength) {
let buf = Buffer.allocUnsafe(contentLength), offset = 0;
return new Promise((resolve, reject) => {
res.on('data', chunk => {
if(chunk.length + offset > contentLength) {
return reject(new Error(`Received too much data, expected ${contentLength} bytes`));
}
chunk.copy(buf, offset);
offset += chunk.length;
});
res.on('end', () => {
if(offset === contentLength) resolve(buf);
else return reject(new Error(`Expected ${contentLength} bytes, received ${offset}`));
});
});
}
Technically this should be on code review, but it's a different audience.
First observation. Correct me if I am wrong, but HTTP.get does not document usage of a return value from the call back. So unless function code uses the await keyword, leaving the callback defined with the async keyword is confusing (you did mention it was of no help).
A second source of confusion for readers is usage of the return reject( ...... ); construct. reject doesn't return a value, and I am unaware of the return value of event listeners being used. So the the order can be reversed by placing the return statement (if actually needed) after the reject call.
Aborting the request can be done in scope of the promise executor by moving let req; back into the executor and explicitly calling .abort as required. Catching the promise should no longer be required. Refactored, the event emitter callbacks could, for example, look like
res.on('data', chunk => {
if(chunk.length + offset > contentLength) {
reject(new Error(`Received too much data, expected ${contentLength} bytes`));
req.abort();
} else {
chunk.copy(buf, offset);
offset += chunk.length;
}
});
res.on('end', () => {
if(offset === contentLength) {
resolve(buf);
} else {
reject( new Error(`Expected ${contentLength} bytes, received ${offset}`));
req.abort();
}
});
Which just leaves the last two return reject.... usages. You could either abort the request in each case, or set an error variable which is checked later:
req = Http.get(url, res => {
let error = null;
// ....
} else {
error = new Error(`Bad Content-Length header: ${contentLengthStr}`);
}
} else {
error = new Error(`Missing Content-Length header not supported`));
}
if( error) {
reject( error);
req.abort();
}
});
At this stage catching promise rejection and re-throwing the error should no longer be needed. Naturally this is untested but hopefully provides some useful feedback.
I have a small problem, this script works perfectly, with one problem, the "runTenant" method is not returning a promise (that needs resolving from "all()".
This code:
Promise.resolve(runTenant(latest)).then(function() {
end();
});
Calls this code:
function runTenant(cb) {
return new Promise(function() {
//global var
if (!Tenant) {
loadCoreModels();
Tenant = bookshelf.core.bs.model('Tenant');
}
new Tenant().fetchAll()
.then(function(tenants) {
if (tenants.models.length == 0) {
return;
} else {
async.eachSeries(tenants.models, function(tenant, next) {
var account = tenant.attributes;
Promise.resolve(db_tenant.config(account)).then(function(knex_tenant_config) {
if (knex_tenant_config) {
db_tenant.invalidateRequireCacheForFile('knex');
var knex_tenant = require('knex')(knex_tenant_config);
var knex_pending = cb(knex_tenant);
Promise.resolve(knex_pending).then(function() {
next(null, null);
});
} else {
next(null, null);
}
});
});
};
});
});
}
The code from runTenant is working correctly however it stalls and does not proceed to "end()" because the promise from "runTenant(latest)" isn't being resolved.
As if it weren't apparent, I am horrible at promises. Still working on getting my head around them.
Many thanks for any help/direction!
You should not use the Promise constructor at all here (and basically, not anywhere else either), even if you made it work it would be an antipattern. You've never resolved that promise - notice that the resolve argument to the Promise constructor callback is a very different function than Promise.resolve.
And you should not use the async library if you have a powerful promise library like Bluebird at hand.
As if it weren't apparent, I am horrible at promises.
Maybe you'll want to have a look at my rules of thumb for writing promise functions.
Here's what your function should look like:
function runTenant(cb) {
//global var
if (!Tenant) {
loadCoreModels();
Tenant = bookshelf.core.bs.model('Tenant');
}
return new Tenant().fetchAll().then(function(tenants) {
// if (tenants.models.length == 0) {
// return;
// } else
// In case there are no models, the loop iterates zero times, which makes no difference
return Promise.each(tenants.models, function(tenant) {
var account = tenant.attributes;
return db_tenant.config(account).then(function(knex_tenant_config) {
if (knex_tenant_config) {
db_tenant.invalidateRequireCacheForFile('knex');
var knex_tenant = require('knex')(knex_tenant_config);
return cb(knex_tenant); // can return a promise
}
});
});
});
}
Your promise in runTenant function is never resolved. You must call resolve or reject function to resolve promise:
function runTenant() {
return new Promise(function(resolve, reject) {
// somewhere in your code
if (err) {
reject(err);
} else {
resolve();
}
});
});
And you shouldn't pass cb in runTenant function, use promises chain:
runTenant()
.then(latest)
.then(end)
.catch(function(err) {
console.log(err);
});
You need to return all the nested promises. I can't run this code, so this isn't a drop it fix. But hopefully, it helps you understand what is missing.
function runTenant(cb) {
//global var
if (!Tenant) {
loadCoreModels();
Tenant = bookshelf.core.bs.model('Tenant');
}
return new Tenant().fetchAll() //added return
.then(function (tenants) {
if (tenants.models.length == 0) {
return;
} else {
var promises = []; //got to collect the promises
tenants.models.each(function (tenant, next) {
var account = tenant.attributes;
var promise = Promise.resolve(db_tenant.config(account)).then(function (knex_tenant_config) {
if (knex_tenant_config) {
db_tenant.invalidateRequireCacheForFile('knex');
var knex_tenant = require('knex')(knex_tenant_config);
var knex_pending = cb(knex_tenant);
return knex_pending; //return value that you want the whole chain to resolve to
}
});
promises.push(promise); //add promise to collection
});
return Promise.all(promises); //make promise from all promises
}
});
}
I have a function that does some operation using an array.
I would like to reject it when the array is empty.
As an example
myArrayFunction(){
return new Promise(function (resolve, reject) {
var a = new Array();
//some operation with a
if(a.length > 0){
resolve(a);
}else{
reject('Not found');
}
};
}
When the reject operation happens I get the following error.
Possibly unhandled Error: Not found
However I have the following catch when the call to myArrayFunction() is made.
handlers.getArray = function (request, reply) {
myArrayFunction().then(
function (a) {
reply(a);
}).catch(reply(hapi.error.notFound('No array')));
};
What would be the correct way to reject the promise, catch the rejection and respond to the client?
Thank you.
.catch takes a function as parameter however, you are passing it something else. When you don't pass a function to catch, it will silently just fail to do anything. Stupid but that's what ES6 promises do.
Because the .catch is not doing anything, the rejection becomes unhandled and is reported to you.
Fix is to pass a function to .catch:
handlers.getArray = function (request, reply) {
myArrayFunction().then(function (a) {
reply(a);
}).catch(function(e) {
reply(hapi.error.notFound('No array')));
});
};
Because you are using a catch all, the error isn't necessarily a No array error. I suggest you do this instead:
function myArrayFunction() {
// new Promise anti-pattern here but the answer is too long already...
return new Promise(function (resolve, reject) {
var a = new Array();
//some operation with a
if (a.length > 0) {
resolve(a);
} else {
reject(hapi.error.notFound('No array'));
}
};
}
}
function NotFoundError(e) {
return e.statusCode === 404;
}
handlers.getArray = function (request, reply) {
myArrayFunction().then(function (a) {
reply(a);
}).catch(NotFoundError, function(e) {
reply(e);
});
};
Which can be further shortened to:
handlers.getArray = function (request, reply) {
myArrayFunction().then(reply).catch(NotFoundError, reply);
};
Also note the difference between:
// Calls the method catch, with the function reply as an argument
.catch(reply)
And
// Calls the function reply, then passes the result of calling reply
// to the method .catch, NOT what you wanted.
.catch(reply(...))