What is a good pattern for "interval with timeout" using Promises - node.js

I'm writing some code to do polling for a resource every N ms which should timeout after M seconds. I want the whole thing to be promise based using Bluebird as much as possible. The solution I've come up with so far uses node's interval, cancellable bluebird promises and bluebird's timeout function.
I'm wondering if there's a better way to do timing out intervals with bluebird and promises in general? Mostly by making sure the interval stops at the point and never continues indefinitely.
var Promise = require('bluebird');
function poll() {
var interval;
return new Promise(function(resolve, reject) {
// This interval never resolves. Actual implementation could resolve.
interval = setInterval(function() {
console.log('Polling...')
}, 1000).unref();
})
.cancellable()
.catch(function(e) {
console.log('poll error:', e.name);
clearInterval(interval);
// Bubble up error
throw e;
});
}
function pollOrTimeout() {
return poll()
.then(function() {
return Promise.resolve('finished');
})
.timeout(5000)
.catch(Promise.TimeoutError, function(e) {
return Promise.resolve('timed out');
})
.catch(function(e) {
console.log('Got some other error');
throw e;
});
}
return pollOrTimeout()
.then(function(result) {
console.log('Result:', result);
});
Output:
Polling...
Polling...
Polling...
Polling...
poll error: TimeoutError
Result: timed out

I would do something like this -
function poll() {
return Promise.resolve().then(function() {
console.log('Polling...');
if (conditionA) {
return Promise.resolve();
} else if (conditionB) {
return Promise.reject("poll error");
} else {
return Promise.delay(1000).then(poll);
}
})
.cancellable()
}
Also be aware of Promise constructor anti-pattern

Rene Wooller makes a really good point:
Warning: unfortunately, recursion in javascript like this will eventually saturate the call stack and result in out of memory exceptions
Even if it doesn't exception, this is wasted space, and the risk of an exception might encourage an overlong polling delay.
I think this is important enough to prefer setInterval:
var myPromise = new Promise((resolve, reject) => {
var id = window.setInterval(() => {
try {
if (conditionA) {
window.clearInterval(id);
resolve("conditionA");
} else if (conditionB) {
throw new Error("conditionB!");
}
} catch(e) {
window.clearInterval(id);
reject(e);
}
}, 1000);
});
There are a few npm packages that address this requirement, of which I like promise-waitfor the best. It's 38 lines long and does the job.
var myPromise = waitFor(() => {
if(conditionA) return true;
if(conditionB) throw new Error("conditionB!");
return false;
});

Related

NodeJs & mongodb - script does not end

I need my script to end/exit after it is finished and after several tests I thought the problem was my mongodb-class, which connection i never closed. (when I commented out the class btw it's usage, the script ran through and exited like I want it)
But after I have implemented a closing-method, my script still is alive and I don't know why?
this is my mongo-class:
const MongoClient = require('mongodb').MongoClient
class MongodbClient {
constructor(cfg) {
// CONNECT TO MONGO-ENGINE
this.client = new MongoClient(cfg.mongoUrl, { useUnifiedTopology: true });
this.client.connect();
// CONNECT TO DB
this.db = this.client.db(cfg.mongoDbName);
}
// Close connection
async end() {
this.client.close()
return new Promise((resolve, reject) => {
resolve(true)
})
}
// .. some mehtods
}
module.exports = {
MongodbClient: MongodbClient
}
in my main-script I call a function dosomething() at which end the script needs to exit:
parser.dosomething().then(async() => {
await mongo.end()
})
but the sctipt still lives? why is that?
Promise-Ception 😯😯
Your end method returns another Promise within a Promise
async end() {
/* ... */
return true
}
👆 This async function returns a Promise by itself. For async functions it's important to return something at some point.
In your dosomething method you do the correct thing and use await to resolve the Promise.
await mongo.end();
However it doesn't stop there. The first Promise (async end) returns another Promise
// ...
return new Promise((resolve, reject) => {
return resolve(true)
});
// ...
To completely resolve everything, your dosomething method should eventually do this :
const anotherPromise = await mongo.end();
await anotherPromise();
By this time you will realize that client.close() as well returns a Promise and should be resolved. The whole thing is a bit messy IMHO.
Simplify things
Try this
async end() {
try {
await this.client.close();
return true;
} catch (ex) {
throw ex;
}
}
parser.dosomething().then(async () => {
try {
const closed = await mongo.end();
console.log("connection closed");
} catch (ex) {
console.log(ex.message);
}
});
Remember to use try ... catch blocks when using async/await . If the result is still the same 👆 then the problem lies somewhere else probably.
Simplify some more
end() { return this.client.close() }
Now your end method just returns the unresolved Promise from client.close. Please Note, I removed the async prefix from the end method as it is not needed.
await mongo.end();

Catch multiple nested asynchronous function errors within a single catch block

The code below is an example of what may take place during development.
With the current code, the outer function may throw an error but in this case wont. However, the nested function WILL throw an error (for examples sake). Once it throws the error it cannot be caught as it is asynchronous function.
Bungie.Get('/Platform/Destiny2/Manifest/').then((ResponseText)=>{
//Async function that WILL throw an error
Bungie.Get('/Platform/Destiny2/Mnifest/').then((ResponseText)=>{
console.log('Success')
})
}).catch((error)=>{
//Catch all errors from either the main function or the nested function
doSomethingWithError(error)
});
What I want is for the outer most function to catch all asynchronous function error's but with this code I cannot. I have tried awaiting the nested function but there may be certain circumstances where it will be quicker to not wait for the function. I also tried to include a .catch() with each nested function but this would require a .catch() for each function that would allhandle the error in the same way e.g. doSomethingWithError().
you only needs return the inner function in the outside function.
see example below:
const foo = new Promise((resolve,reject) =>{
setTimeout(() => resolve('foo'), 1000);
});
foo.then((res)=>{
console.log(res)
return new Promise((resolve,reject)=>{
setTimeout(() => reject("bar fail"), 1000);
})
}).catch((e)=>{
// your own logic
console.error(e)
});
this is called promise chaining. see this post for more info https://javascript.info/promise-chaining
if you have multiple promises can do something like:
const foo1 = new Promise((resolve,reject) =>{
setTimeout(() => resolve('foo1'), 1000);
});
const foo2 = new Promise((resolve,reject) =>{
setTimeout(() => resolve('foo2'), 2000);
});
const foo3 = new Promise((resolve,reject) =>{
setTimeout(() => reject('foo3'), 3000);
});
const bar = new Promise((resolve,reject) =>{
setTimeout(() => resolve('bar'), 4000);
});
foo1
.then((res)=>{
console.log(res)
return foo2
})
.then((res)=>{
console.log(res)
return foo3 // throws the error
})
.then((res)=>{
console.log(res)
return bar
})
.catch((e)=>{
// every error will be cached here
console.error(e)
});
I would aim to use async / await unless you have very particular reasons, since it avoids callback hell and makes your code simpler and more bug free.
try {
const response1 = await Bungie.Get('/Platform/Destiny2/Manifest/');
const response2 = await Bungie.Get('/Platform/Destiny2/Mnifest/');
console.log('Success');
} catch (error) {
doSomethingWithError(error);
}
Imagine each Bungie call takes 250 milliseconds. While this is occurring, NodeJS will continue to execute other code via its event loop - eg requests from other clients. Awaiting is not the same as hanging the app.
Similarly, this type of code is used in many browser or mobile apps, and they remain responsive to the end user during I/O. I use the async await programming model in all languages these days (Javascript, Java, C#, Swift etc).
Try this:
let getMultiple = function(callback, ... keys){
let result = [];
let ctr = keys.length;
for(let i=0;i<ctr;i++)
result.push(0);
let ctr2 = 0;
keys.forEach(function(key){
let ctr3=ctr2++;
try{
Bungie.Get(key, function(data){
result[ctr3] = data;
ctr--;
if(ctr==0)
{
callback(result);
}
});
} catch(err) {
result[ctr3]=err.message;
ctr--;
if(ctr==0)
{
callback(result);
}
}
});
};
This should get all your data requests and replace relevant data with error message if it happens.
getMultiple(function(results){
console.log(results);
}, string1, string2, string3);
If the error causes by requesting same thing twice asynchronously, then you can add an asynchronous caching layer before this request.

How to make a promise based function to be executed a few times with setTimeout before giving up in node 6 (i.e. no javascript specs for async/await)

I need to retrieve the actual value from a promise based function in a node 6 environment (Azure Functions), so I used co (https://www.npmjs.com/package/co) via generators (instead of the async/await paradigm) to handle the inner promise.
I need also to retry a few times that co/promise function using setTimeout before giving up definitively.
I am currently not able to make the following code work as expected. I am not sure where is the problem, but I can not "yield from the promise returned by co", so in the end the array that is passed around the recursive levels of the stack contains promises of values (1/0) rather than the actual values.
This is the wrapper for the "promise based function" that is handled with a try/catch to make sure we actually always return either 1 or 0.
const wannabeSyncFunc = () => {
console.log("outside co...");
return co(function *(){
console.log("inside co...");
try {
console.log("yielding...");
// promise that could be rejected hence try/catch
//
// I can not change this returned promise, so I must treat it
// as a promise that could potentially be rejected
let stuff = yield Promise.resolve();
console.log("stuff?", stuff);
console.log("returning 1");
return 1;
} catch (err) {
console.log("returning 0");
return 0;
}
console.log("after try/catch...");
});
}
This is the recursive/settimeout function that is supposed to try a few times before giving up.
const retryIntervalInMillis = 50;
const wannabeRecursiveFunc = (currTimes, attemptsArray) => {
return co(function *(){
console.log("Curr attemptsArray:", attemptsArray);
console.log("Curr attemptsArray[attemptsArray.length - 1]:", attemptsArray[attemptsArray.length - 1]);
console.log("Curr Promise.resolve(attemptsArray[attemptsArray.length - 1]):", Promise.resolve(attemptsArray[attemptsArray.length - 1]));
if (attemptsArray[attemptsArray.length - 1] == Promise.resolve(1)) {
console.log("Found the solution, returning straight away!")
return attemptsArray;
}
if (currTimes <= 0) {
console.log("Expired acquiring recursion");
return attemptsArray;
}
currTimes--;
const currValue = wannabeSyncFunc();
console.log(`First: currTimes: ${currTimes} currValue: ${currValue} curr attemptsArray: ${attemptsArray}`);
attemptsArray.push(currValue);
if (currValue === 1) {
return attemptsArray;
}
console.log(`Then: currTimes: ${currTimes} curr attemptsArray: ${attemptsArray}`);
return yield setTimeout(wannabeRecursiveFunc, currTimes*retryIntervalInMillis, currTimes, attemptsArray);
// return Promise.all(attemptsArray);
});
}
I've tried to invoke this in a few different ways like:
const numberOfAttempts = 3;
let theArray = wannabeRecursiveFunc(numberOfAttempts, []);
console.log(">>>", theArray);
Or assuming wannabeRecursiveFunc to return a promise and .then after the promise trying to print theArray.
I keep seeing inside the array these elements Promise { 1 } when printing it, but I would like to see either 1 or 0, so I hope those checks before the recursion could work as expected. At the moment those check don't work I think because I am comparing Promise { 1 } with 1.
However, I am not sure this is the reason why the whole thing is not working, and I am not even sure how to fix this. I am not sure whether co is needed (even in the node.js v6 environment), and how to make this promise/setTimeout work as expected.
I think I understand your objective: invoke a function that might fail, if it fails, wait a little bit and retry it. Do all of that with promises.
Here's a couple tools:
a promisified version of setTimeout...
function timeoutPromise(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
timeoutPromise(1000).then(() => {
console.log('time out expired');
});
A promise-returning dummy function that sometimes fails...
function fnThatMightFail() {
return new Promise((resolve, reject) => {
let fail = Math.random() < 0.40;
(fail)? reject('bad') : resolve('good');
});
}
fnThatMightFail().then(result => {
console.log(result);
}).catch(error => {
console.log(error);
});
And then, I think here's the recursive idea you're looking for. Pass in a function and a wait time between attempts, call recursively until we succeed...
function fnThatMightFail() {
return new Promise((resolve, reject) => {
let fail = Math.random() < 0.40;
(fail)? reject('bad') : resolve('good');
});
}
function timeoutPromise(ms) {
return new Promise((resolve) => {
setTimeout(() => resolve(), ms);
});
}
function fnRetryer(fn, tries, wait) {
if (tries <= 0) return Promise.reject('bad');
console.log('attempting fn');
return fn().then(result => {
console.log(`success: ${result}`);
return result;
}).catch(error => {
console.log(`error: ${error}, retrying after ${wait}ms`);
return timeoutPromise(wait).then(result => {
console.log(`${wait}ms elapsed, recursing...`);
return fnRetryer(fn, tries-1, wait);
});
});
}
fnRetryer(fnThatMightFail, 5, 1000).then(result => {
console.log(`we tried (and maybe tried) and got ${result}`);
}).catch(error => {
console.log('we failed after 5 tries, waiting 1s in between each try');
});
Note that you could add a parameter for a max number of attempts, decrement that on each recursive call and then don't recurse if that gets to zero. Also note, on the recursive call, you might opt to lengthen the wait time.

How to use promises to simulate a synchronous for loop

I cannot get Promises to work (the way I think they should). The following code executes almost immediately. ie. does not wait the 4 seconds before logging each number to the console.
function do_nothing(a) {
return new Promise(function(accept, reject){
console.log(a)
setTimeout(accept(parseInt(a)+1), 4000);
});
}
function do_til_finish(i) {
if (parseInt(i) < 5) {
do_nothing(i)
.then(j => do_til_finish(j))
.catch(j =>{})
} else {
console.log('All done');
}
}
do_til_finish(0);
jsfiddle
What am I missing?
btw. I do not want to run the loop asynchronously as the statements will use all the memory and freeze the server.
This is not a webserver so I dont need to worry about frustrating users.
Thanks in advance
You are not using a function inside setTimeout.
try this:
function do_nothing(a) {
return new Promise(function(accept, reject){
console.log(a)
setTimeout(function(){accept(parseInt(a)+1)}, 4000);
});
}
function do_til_finish(i) {
if (parseInt(i) < 5) {
do_nothing(i)
.then(j => do_til_finish(j))
.catch(j =>{})
} else {
console.log('All done');
}
}
console.log("\n");
do_til_finish(0);
First you need to pass a function to setTimeout not to call it.
Second, you need to return a promise from do_til_finish to chain further calls.
BTW you don't need to parseInt.
function do_nothing(a) {
return new Promise(function(accept, reject){
console.log(a)
setTimeout(accept, 4000, a + 1);
});
}
function do_til_finish(i) {
if (i < 5) {
return do_nothing(i)
.then(do_til_finish)
.catch(j =>{})
} else {
console.log('All done');
}
}
console.log("\n");
do_til_finish(0);
The problem is that you used the setTimeout function without a callback and passed directly a value, wich is not working as expected. Any way the design is pretty messy I suggest you to look at async module for better asynchronous designs patterns.
function doNothing(a) {
return new Promise((resolve) => {
console.log(a);
setTimeout(() => resolve(parseInt(a, 10) + 1), 4000);
});
}
function doUntilFinish(i) {
if (parseInt(i, 10) < 5) {
doNothing(i)
.then(j => doUntilFinish(j))
.catch(err => console.log(err));
} else {
console.log('All done');
}
}
doTillFinish(0);

NodeJS fs.appendFile not working within promise in mocha

I want to keep a log during my integration test suite. I'm testing that every 'item' is being compiled and logging how much time it took. I'm using node 4.3.
First of all I create the log file:
before(function() {
if (!fs.existsSync("./log.csv")) {
fs.writeFile("./log.csv", "Name; Time");
}
});
Then within each it block I would do this:
for (const item of items) {
it('compiles', function() {
return item.testCompile();
});
}
And item class has these methods:
testCompile() {
return this
.buildItem()
.then(result => {
// whatever testing stuff
});
}
buildItem() {
return this
.internalLogicForCompiling()
.then(result => {
// This is not appending anything
fs.appendFile("./log.csv", `${item.name}; ${result.compileTime}`);
return result;
});
}
So far the file is created but never updated... Any clue what I'm doing wrong?
PS: I assume if the file doesn't exists, fs should throw an error, however it doesn't.
Your code is generally ignoring the fact that your fs calls are asynchronous. Promises are not magic. If you use code that is asynchronous but does not use promises, you need to do more than plop that code in a promise can call it done.
The easiest way to deal with the issue would be to use fs.writeFileSync and fs.appendFileSync instead of the calls you make. Otherwise, you should write your before like this:
before(function(done) {
if (!fs.existsSync("./log.csv")) {
fs.writeFile("./log.csv", "Name; Time", done);
}
});
I've just added the done callback.
And buildItem could be something like this so that the promise it returns won't resolve before appendFile is done doing its work:
buildItem() {
return this
.internalLogicForCompiling()
.then(result => {
return new Promise((resolve, reject) => {
fs.appendFile("./log.csv", `${item.name}; ${result.compileTime}`, (err) => {
if (err) {
reject(err);
return;
}
resolve(result);
});
});
});
}

Resources