Does promise resolved in n-th setTimeout cause memory leak? - javascript

I can see in Chrome task manager that the tab in which following code is running eats more and more memory, and it is not released until the promise is resolved
UPDATE
Main idea here is to use a single 'low level' method which would handle "busy" responses from the server. Other methods just pass url path with request data to it and awaiting for a valuable response.
Some anti-patterns was removed.
var counter = 1
// emulates post requests sent with ... axios
async function post (path, data) {
let response = (counter++ < 1000) ? { busy: true } : { balance: 3000 }
return Promise.resolve(response)
}
async function _call (path, data, resolve) {
let response = await post()
if (response.busy) {
setTimeout(() => {
_call(path, data, resolve)
}, 10)
throw new Error('busy')
}
resolve(response.balance)
}
async function makePayment (amount) {
return new Promise((resolve, reject) => {
_call('/payment/create', {amount}, resolve)
})
}
async function getBalance () {
return new Promise((resolve, reject) => {
_call('/balance', null, resolve)
})
}
makePayment(500)
.then(() => {
getBalance()
.then(balance => console.log('balance: ', balance))
.catch(e => console.error('some err: ', e))
})

The first time you call _call() in here:
async function getBalance () {
return new Promise((resolve, reject) => {
_call('/balance', null, resolve)
})
}
It will not call the resolve callback and it will return a rejected promise and thus the new Promise() you have in getBalance() will just do nothing initially. Remember, since _call is marked async, when you throw, that is caught and turned into a rejected promise.
When the timer fires, it will call resolve() and that will resolve the getBalance() promise, but it will not have a value and thus you don't get your balance. By the time you do eventually call resolve(response.balance), you've already called that resolve() function so the promise it belongs to is latched and won't change its value.
As others have said, there are all sorts of things wrong with this code (lots of anti-patterns). Here's a simplified version that works when I run it in node.js or in the snippet here in the answer:
function delay(t, val) {
return new Promise(resolve => {
setTimeout(resolve.bind(null, val), t);
});
}
var counter = 1;
function post() {
console.log(`counter = ${counter}`);
// modified counter value to 100 for demo purposes here
return (counter++ < 100) ? { busy: true } : { balance: 3000 };
}
function getBalance () {
async function _call() {
let response = post();
if (response.busy) {
// delay, then chain next call
await delay(10);
return _call();
} else {
return response.balance;
}
}
// start the whole process
return _call();
}
getBalance()
.then(balance => console.log('balance: ', balance))
.catch(e => console.error('some err: ', e))

Related

JS Promise function call that returns object [duplicate]

This question already has answers here:
await setTimeout is not synchronously waiting
(2 answers)
Closed last month.
I have a async fetch function that waits 2 seconds and returns a object:
async function fetch() {
var object;
await setTimeout(() => { object = { name: 'User', data: 'API Data' } }, 2000);
return object;
}
I want to display the object when the initialization is completely done (after 2 seconds)
fetch().then((val) => {
console.log("DONE!");
console.log(val.name);
}).catch((err) => {
console.log("ERROR!");
console.log(err);
});
The code prints both DONE and ERROR Cannot read properties of undefined (reading 'name')
I have tried with Promise, no luck
let promise = new Promise((resolve, reject) => {
let request = fetch();
if (request !== undefined)
resolve(request);
else
reject(request);
}).then((val) => {
console.log(val);
});
How can I properly check that fetch() has returned a value before printing without changing the inside of the function. I can delete the async and await in it but I am unable to edit it (I.E. adding a Promise inside)
Based on requirement
I can delete the async and await in it (fetch function) but I am unable to edit it (I.E. adding a Promise inside)
The only way I see is to override window.setTimeout function to make it to return a promise. That way you will be able to await it and there will be no need to modify your fetch function.
const oldTimeout = window.setTimeout;
window.setTimeout = (fn, ms) => {
return new Promise((resolve, reject) => {
oldTimeout(() => {
fn();
resolve();
}, ms);
});
};
async function fetch() {
var object;
await setTimeout(() => {
object = { name: "User", data: "API Data" };
}, 2000);
return object;
}
fetch()
.then((val) => {
console.log("DONE!");
console.log(val.name);
})
.catch((err) => {
console.log("ERROR!");
console.log(err);
});
NOTE: For anyone without this requirement - please, use other answers to this question or check await setTimeout is not synchronously waiting for additional details/explanations. This kind of overridings are very confusing due to everyone expect common and well-known functions to behavior in a way described in the docs.
You cannot await the setTimeout function, this is because your function returns undefined. You have used the promise in the wrong way. Below code will fix your issue.
function fetch() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ name: "User", data: "API Data" });
}, 2000);
});
}
fetch()
.then((val) => {
console.log("DONE!");
console.log(val.name);
})
.catch((err) => {
console.log("ERROR!");
console.log(err);
});
And remember that there is no need to change the setTimeout function.
The problem is that setTimeout does not actually return a promise, which means you cannot use await with setTimeout, that's why the var object; is returned instantly as undefined.
To solve this issue, you simply need to wrap setTimeout around a promise.
Like so:
function setTImeoutAwait(time) {
return new Promise((resolve) => {
setTimeout(resolve, time);
});
}
You can then use it like this:
async function fetch() {
var object;
await setTImeoutAwait(1000).then(() => {
object = { name: "test" };
});
return object;
}

Skip waiting if async function (Promise) is taking too much time [duplicate]

This question already has answers here:
Timeout in async/await
(3 answers)
Closed 1 year ago.
In my express application, I am making call to 2 APIs. The 2nd API is managed by 3rd party and sometimes can take more than 5 seconds to respond. Hence, I want to just wait for 1 second for the API to respond. If it does not, just proceed with data from 1st API.
Below is the mock-up of the functions being called.
I am thinking to use setTimeout to throw error if the API takes more than 1 second. If the API responds within 1 second then I just cancel the setTimeout and no error is ever thrown.
But there is problem with this approach:
setTimeout errors cannot be catched using try...catch block.
I cannot use axios's timeout option, as I still need to wait for the 2nd API to finish the processing and save the data in the DB. This will ofcourse, can happen later, when the 2nd API call finishes.
// Function to simulate it's taking time.
async function cWait(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Track whether it took time.
let isTimeOut = false
async function test() {
console.log('starting')
try {
const one = await apiCall1()
const myt = setTimeout(() => {
console.log('Its taking time, skip the 2nd API Call')
isTimeOut = true
throw new Error('Its taking time')
})
const two = await apiCall2(myt)
} catch (error) {
console.log(error)
}
saveInDB({ ...one, ...two })
}
async function apiCall2(timeOutInstance) {
console.log('start-apiCall')
await cWait(1800)
clearTimeout(timeOutInstance)
if (isTimeOut) saveInDB()
console.log('done-apiCall')
}
async function apiCall1() {
await cWait(5)
}
async function saveInDB(data) {
console.log('saveInDB')
}
test()
please note, this is not the answer as it was when it was accepted
as I misread the question and failed to call saveInDB in a timed out
situation
Promise.race seems perfect for the job
Also, you'd actually use your cWait function, not for mock-up, but to actually do something useful ... win the race :p
const api2delay = 800;
async function cWait(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
const TIMEDOUT = Symbol('TIMEDOUT');
async function cReject(ms) {
return new Promise((_, reject) => setTimeout(reject, ms, TIMEDOUT));
}
function apiCall2timeout(timeoutCallback) {
const resultApi2 = apiCall2();
const timeout = cReject(1000);
return Promise.race([resultApi2, timeout])
.catch(e => {
if (e === TIMEDOUT) {
resultApi2.then(timeoutCallback);
} else {
throw e;
}
});
}
async function test() {
console.log('starting')
let one, two;
try {
one = await apiCall1();
two = await apiCall2timeout(saveInDB);
} catch (error) {
console.log('error', error)
}
saveInDB({
...one,
...two
})
}
async function apiCall2() {
console.log('start-apiCall2')
await cWait(api2delay)
console.log('done-apiCall2')
return {
api2: 'done'
}
}
async function apiCall1() {
await cWait(5)
return {
api1: 'done'
}
}
async function saveInDB(data) {
console.log('saveInDB', data)
}
test()
Note: I changed where one and two were declared since const is block scoped
I you run with await cWait(800) in apiCall2, the saveInDB will run with both data.
But if you run await cWait(1800), the saveInDB will run 2 times.
// Function to simulate it's taking time.
async function cWait(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// https://italonascimento.github.io/applying-a-timeout-to-your-promises/
const promiseTimeout = function (ms, promise) {
// Create a promise that rejects in <ms> milliseconds
let timeout = new Promise((resolve, reject) => {
let id = setTimeout(() => {
clearTimeout(id);
reject('Timed out in ' + ms + 'ms.')
}, ms)
})
// Returns a race between our timeout and the passed in promise
return Promise.race([
promise,
timeout
])
}
// Track whether it took time.
let isTimeOut = false
async function test() {
console.log('starting')
const one = await apiCall1() // get data from 1st API
let two = {};
try {
two = await promiseTimeout(1000, apiCall2())
} catch (error) {
isTimeOut = true;
console.log(error)
}
saveInDB({ ...one, ...two })
}
async function apiCall2() {
console.log('start-apiCall')
await cWait(800)
console.log('done-apiCall', isTimeOut)
if (isTimeOut) {
saveInDB({ 2: 'two' })
}
return { 2: 'two' }
}
async function apiCall1() {
await cWait(5)
return { 1: 'one' }
}
async function saveInDB(data) {
console.log('saveInDB', data)
}
test()

JavaScript: Retry asynchronous readFile() function

I want to read a file with javascript. This file has to be downloaded first and is not immediately available. That means if an access to this file with readFile() fails and ends up in the catch block, then this action should be repeated.
const fse = require('fs-extra')
let retries = 0
function read_a_file(path) {
return fse.readFile(path)
.catch(error => {
if (retries < 5) {
retries++
console.log('Retry', retries)
setTimeout(() => read_a_file(path), 1000);
} else {
console.log(error.message)
}
})
}
read_a_file('path/to/file').then(content => {
console.log('Content:', content)
})
The output of this function is:
Retry 1
Content: undefined
Retry 2
Retry 3
Retry 4
Retry 5
ENOENT: no such file or directory, open 'path/to/file'
I would now have expected the content (in this case undefined) to be output at the end of all retries. Why is it issued before the first repetition?
You are creating new promises in the setTimeout that have nothing to do with the initial promise you returned.
I would creae a Promise and return that. The readFile would call that promise's resolve or reject.
const fse = require('fs-extra')
function read_a_file(path) {
let retries = 0
const readFile(resolve, reject) {
fse.readFile(path)
.then(resolve)
.catch(error => {
if (retries < 5) {
retries++
console.log('Retry', retries)
setTimeout(() => readFile(resolve, reject), 1000);
} else {
console.log(error.message)
reject(error);
}
})
}
return new Promise((resolve, reject) => readFile(resolve, reject));
}
read_a_file('path/to/file')
.then(content => {
console.log('Content:', content)
})
.catch(e => {
console.log(e);
})
You are using .then in the first call only, so the content logging function will only work once.
read_a_file('path/to/file').then(content => {
console.log('Content:', content)
})
Your other call to the function is setTimeout(() => read_a_file(path), 1000);, if you add a .then after that as well, it will also log it after the other promises resolve. You'll get 5 of those outputs then.

Async Job queue creator which works one job at the time

I need to write a JavaScript module that exports a function createJobQueue, which creates an asynchronous job queue.
This job queue should have 3 methods:
addJob
cancelJob
processAllJob
addJob adds a job to the end of the queue. It should return a promise that resolves with the value returned by job, whenever job ends up getting executed. (But addJob itself should not trigger execution of any jobs.) If job throws an error, then the promise returned by addJob should be rejected.
cancelJob removes a job from the queue. This should reject the promise returned by addJob. If no matching job is found, it does nothing.
When processAllJobs is called, the queue should process jobs (by invoking them) one-at-a-time in FIFO order until there are none left, then resolve with the number of jobs successfully processed (i.e., that did not reject or throw an error).
If any job cannot be processed (because job rejects or throws an error when invoked) the promise returned by addJob should be rejected. However, this should not stop processAllJobs from processing the rest of the queue.
What I tried so far
addJob is working good but I can't make cancelJob to reject job added by addJob and I can't make proccessAllJobs work one-at-a-time.
Here's the code:
function createJobQueue() {
return new class jobQueue{
constructor() {
this.jobs = [];
}
addJob(job) {
return new Promise((resolve, reject) => {
if (job()) {
this.jobs.push(job);
//console.log(this.jobs)
resolve(job());
}
throw new Error;
reject();
})
}
cancelJob(job) {
return new Promise(resolve => {
const index = this.jobs.findIndex(j => j === job);
if (index === -1) {
resolve();
} else {
this.jobs = this.jobs.filter((job, i) => i !== index);
}
})
}
processAllJobs() {
return new Promise(resolve => {
let count = 0;
this.jobs.forEach((f, index) => {
if(index === this.jobs.length-1) {
f()
.then(() => {
++count
})
.catch((e) => {
resolve(count);
})
}
//console.log("count", count);
f()
.then(() => {
count++
})
.catch()
})
resolve(count);
});
}
getJobs() {
console.log(this.jobs, "Next only jobs")
console.log(this.jobs.forEach(f => f().then(console.log)));
}
}
}
const queue = createJobQueue();
queue.addJob(function() {
return Promise.resolve("One")
});
queue.addJob(function() {
return Promise.resolve("Two")
});
queue.addJob(function() {
return Promise.resolve("Three")
});
//queue.getJobs();
console.log(queue.processAllJobs());
module.exports = { createJobQueue };
Your implementation of addJobs goes against the requirements: it does execute the job, while it shouldn't. Realise that the promise constructor callback is executed immediately (synchronously).
Secondly, the trouble you have in sequencing the job execution can be more easily avoided by using async/await.
I would also suggest creating the job queue as a Map keyed by job: that way you can easily find an entry by a given job.
Finally, as you need to be able to resolve/reject a job's promise at some later time, when processAllJobs is called, you will need a reference to the appropriate resolve/reject functions. You could store those in the job queue: one pair per job.
Here is how it could look:
class JobQueue{
constructor() {
this.jobs = new Map; // so we can key by job.
}
addJob(job) {
return new Promise((resolve, reject) =>
this.jobs.set(job, {resolve, reject})
);
}
cancelJob(job) {
let deferred = this.jobs.get(job);
if (this.jobs.delete(job)) deferred.reject("job cancelled");
}
async processAllJobs() {
let count = 0;
for (let [job, {resolve, reject}] of this.jobs) {
try {
resolve(await job());
count++;
} catch(e) {
reject(e);
}
}
return count;
}
}
function createJobQueue() {
return new JobQueue;
}
// Demo
const queue = createJobQueue();
// Instead of Promise.resolve, use a bit more flexible
// promise-generation function:
const delay = (ms, value, err) => new Promise((resolve, reject) =>
setTimeout(() => {
if (err) reject(err); else resolve(value);
console.log(err || value);
}, ms)
);
queue.addJob(() => delay(500, "One"));
queue.addJob(() => delay(500, "Two"));
// add an example of a rejecting promise:
queue.addJob(() => delay(500, null, "Failing job"));
queue.addJob(() => delay(500, "Three"));
queue.processAllJobs().then(count =>
console.log("count:", count)
);

Promise.all rollback successful promises' actions on failure

I am executing multiple promises with the following snippet:
await Promise.all([promise1, promise2, promise3]);
What I would like to achieve is to rollback the effects of the successful promises on the case of a failure from Promise.all().
In more specific terms, this means that the above will do some file encryptions, but if one fails, I would like to delete the other two (or one) files that were encrypted successfully so as to have consistent and clean file groups.
From what I've read this means that I would need two steps:
1. Catching the errors for each promise so that Promise.all() won't throw an error.
2. The puzzling part: Having another Promise.all() sort of:
await Promise.all([rollbackPromise1, rollbackPromise2, rollbackPromise3]);
This one seems to be the tricky part: Should I execute all the rollbacks independent of the promise that failed? This means that I should do another catch for every error such that the Promise.all() waits for every rollback to finish.
Is this the best way to do this, I find it pretty inefficient and ugly in terms of code.
You could create your own function implementing the asynchronous call of the functions and performing a rollback if required.
// Function that'll perform a promise.all and rollback if required
async function allWithRollback(promises) {
// using the map we are going to wrap the promise inside of a new one
return Promise.all(promises.map(([
func,
rollbackFunc,
], xi) => ((async() => {
try {
await func;
console.log('One Function succeed', xi);
} catch (err) {
console.log('One Function failed, require rollback', xi);
await rollbackFunc();
}
})())));
}
// Call the custom Promise.all
allWithRollback([
[
// First param is the promise
okPromise(),
// Second param is the rollback function to execute
() => {},
],
[okPromise(), () => {}],
[errPromise(), rollback1],
[errPromise(), rollback2],
[okPromise(), () => {}],
]);
// ---------
async function okPromise() {
return true;
}
async function errPromise() {
throw new Error('no one read this');
}
async function rollback1() {
console.log('Performed the rollback1');
}
async function rollback2() {
console.log('Performed the rollback2');
}
You can create a naive solution as follows:
const errorHandlers = []
function enc1 () {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('str')
}, 1000)
errorHandlers.push(() => {
console.log('handler 1')
})
})
}
function enc2 () {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('str')
}, 2000)
errorHandlers.push(() => {
console.log('handler 2')
})
})
}
function enc3 () {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject('str')
}, 3000)
errorHandlers.push(() => {
console.log('handler 3')
})
})
}
Promise.all([enc1(), enc2(), enc3()]).then(() => {
console.log('all resovled')
}).catch((e) => {
errorHandlers.forEach(handler => handler(e))
})
It'd give you option to handle the 'global' error in each promise. Before creating promise all, you can reset the errorHandlers to prevent multiple errorHandler execution

Categories