This question already has an answer here:
How to sequentially run promises with Q in Javascript?
(1 answer)
Closed 7 years ago.
I'm trying to execute a series of functions synchronousl usingy. Each function is supposed to be delayed by 3 seconds before invoking the next one.
I must be doing something wrong because they are all invoked at the same time after 3 seconds instead of in order.
What am I doing wrong?
var tasks = []
allGroups.forEach(function(group){
tasks.push(deleteFromGroup(group))
})
tasks.reduce(function(cur, next) {
return cur.then(next);
}, Promise.resolve()).then(function() {
console.log("all executed")
});
})
}
function deleteFromGroup(group){
return new Promise(function(resolve, reject) {
setTimeout(function(){
console.log(group.id)
resolve()
}, 3000);
})
}
The way you're creating your tasks array inevitably results in the timeouts all hitting at (about) the same time, because you're creating the tasks simultaneously in that very first .forEach loop.
To achieve the effect you require you need to actually not create the next task until the current one is resolved. Here's a pseudo-recursive way of achieving that:
return new Promise(resolve, reject) {
var groups = allGroups.slice(0); // clone
(function loop() {
if (groups.length) {
deleteFromGroup(groups.shift()).catch(reject).then(loop);
} else {
console.log("all executed")
resolve();
}
})();
});
p.s. in practise you might actually want to incorporate the 3s timeout directly into the loop, instead of into deleteFromGroup - as written the code above (and your original code) won't show "completed" until 3s after the final delete call, but I expect it's really supposed to occur immediately at the end.
You do not need to resort to callbacks and explicit construction here. You can in fact use a for loop - but not on the actions since a promise is an already started operation.
All you need to do is merge your two loops:
allGroups.reduce(function(cur, next) {
return cur.then(function(){ return deleteFromGroup(next) });
}, Promise.resolve()).then(function() {
console.log("all executed")
});
Related
I have the following code:
function MyPromise(configFunction) {
let nextSuccessCallBack = undefined;
let nextResolve = undefined;
configFunction(function(message){
setTimeout(function(){
if(nextSuccessCallBack) {
var result = nextSuccessCallBack(message);
if(result && result.then) {
result.then(nextResolve);
} else {
nextResolve && nextResolve(result);
}
}
})
});
return {
then: function(successCallback) {
nextSuccessCallBack = successCallback;
return new MyPromise(function(resolve) {
nextResolve = resolve;
})
}
}
}
new MyPromise(function(resolve, reject) {
resolve('new message');
}).then(function(message) {
console.log(message);
return 'another message'
}).then(function(message) {
console.log(message)
console.log('here')
})
In this example, it seems that the nextSuccessCallBack is set to be the callback functions within .then, then its value within the setTimeout function is now populated. However, this confuses me. I thought that when we reach the return statement in the constructor, we return the object and effectively stop the function? If that the case then how does the program even get to the setTimeout?
This is not a correct Promise implementation. It clearly has no capabilities for rejections, but also for the implemented fulfilment feature it does not comply with the Promises/A+ specification. Just to give two examples:
It does not comply with rule 2.1.2.2
When fulfilled, a promise must have a value, which must not change.
....nor with rule 2.2.2.3:
If onFulfilled is a function it must not be called more than once.
In your implementation, if you would add a second call to resolve:
new MyPromise(function(resolve, reject) {
resolve('new message'); resolve('new message2');
}).then((function(message) {
console.log(message);
// ... etc
})
...then both calls to resolve would fire the then callback, and show that the promised value was modified after the first time it was set. This is in complete violation of the principle of promises: promises can only resolve once.
It does not comply with rule 2.2.6:
then may be called multiple times on the same promise.
If/when promise is fulfilled, all respective onFulfilled callbacks must execute in the order of their originating calls to then.
This does not happen if we use the following code:
let p = new MyPromise(resolve => resolve("value"));
p.then(console.log); // This callback is called
p.then(console.log); // This callback is not called -> violation!
These are basic shortcomings, and this is just the tip of the iceberg.
If you are interested in how it could be implemented in compliance with Promise/A+, then have a look at one I did a few years ago with a step-by-step explanation.
As to your question
how does the program even get to the setTimeout?
When your main code executes this:
new MyPromise(function(resolve, reject) {
resolve('new message');
})
...then the parameter variable configFunction is initialised with that callback function. The MyPromise constructor calls it, passing it the following callback as first argument:
function(message){
setTimeout(function(){
if(nextSuccessCallBack) {
var result = nextSuccessCallBack(message);
if(result && result.then) {
result.then(nextResolve);
} else {
nextResolve && nextResolve(result);
}
}
})
}
So that means that resolve in your main code's callback function is initialised with the above callback function. Then your main code's callback function calls resolve with a string argument. Since resolve is the above callback function, we can see it gets executed with message initialised to "new message". This function executes setTimeout.
The code in the snippet does solve the purpose but, it is confusing as is a bad implementation of a Promise according to the way MDN Uses Promises in JavaScript for better code efficiency and understanding.
The above code can also be written this way:
const examplePromise = new Promise((resolve, reject) => {
resolve("Another Message");
});
console.log("New Message");
examplePromise.then((message) => {
console.log(message);
});
Now, for better understading of the concept of Promises in JavaScript, I'll take another example:
Let's say I want to create a program which says hello and then after 2 seconds says How Are You?
There are two ways of solving this
Just using the setTimeout() function which executes a function after certain period of time
Using promises with setTimeout()
Case I (Using setTimeout()):
console.log("hello"); //Saying hello for the first time
setTimeout(function () {
console.log("How Are You?");
}, 2000); //2000 milliseconds is 2 seconds
Case II (Using Promises)
console.log("hello");
const example = new Promise((resolve) => {
setTimeout(() => {
resolve("How Are You?");
}, 2000);
});
example.then((message) => {
console.log(message);
});
Now, for simple cases like logging a message after 2 seconds do not require Promises. You may use them (No problem) but actually, Promises are used in cases where, you wait for a function to execute a Database Query or you need to wait for a server to give you some HTML Response or while using WebAPIs, etc.
P.S: setTimeout() is just an example. It is not a rule that you always have to use setTimeout() while writing a Promise. If you are fetching data using a WebAPI, you usually write $.ajax(...).then(...) or fetch(...).then(...) instead of setTimeout()
This question already has answers here:
Correct way to write a non-blocking function in Node.js
(2 answers)
Closed 2 years ago.
I have a node js program. I want to run 2 countdown functions asynchronously by using Promise.all. It is expected that the two functions will count down together, and the program ends within 5 seconds. But they run sequentially and the program ends within 10 seconds. How to run them asynchronously? Thank you for your help.
const delayOneSecond = () => {
let start = new Date().getTime();
while (new Date().getTime() - start < 1000) { }
return;
}
const OK = true;
const func = (name, num) => {
return new Promise(
function (resolve, reject) {
if (OK) {
for(let i=1; i<=num; i++){
delayOneSecond();
console.log(`[${name}] - ${num - i}`);
}
resolve('OK');
} else {
reject(new Error('Not OK'));
}
});
}
Promise.all([func('xxx', 5), func('ooo', 5)])
.then((res) => { console.log(res); })
Your while loop is a loop that keeps JavaScript busy during one second. During that time no other JavaScript code can execute, no matter what else you had planned for execution. In your case this means that the second func('ooo', 5) does not get launched until the first call has returned.
In practice, the callback you provide to new Promise should not be a blocking piece of code: it should execute relatively quickly and return. It's job is mostly to call some (low-level) API that will trigger an asynchronous event, to which your code will listen. At that time resolve can be called.
You can use the Web API for this (or some other asynchronous library's API), which gives you setTimeout: that function will allow you to be notified when the delay (e.g. 1 second) has passed, but still executes the rest of your code to completion (which can possibly also call setTimeout).
If you would resolve a promise when setTimeout calls its callback, then you have a useful, non-blocking implementation of delayOneSecond. It can then be combined easily with await:
const delayOneSecond = () => new Promise(resolve => setTimeout(resolve, 1000));
const func = async (name, num) => {
for(let i=1; i<=num; i++){
await delayOneSecond();
console.log(`[${name}] - ${num - i}`);
}
return "OK";
}
Promise.all([func('xxx', 5), func('ooo', 5)])
.then((res) => { console.log(res); })
Although it may seem here that the execution of func still takes 5 seconds before it returns, this is actually not true. It returns when it arrives at the first await. It returns a promise at that time (so without any delay), and execution can continue with the second call of func.
The two function execution contexts get restored when their delayOneSecond() promises resolve, i.e. after (at least) one second. Only then their for loops continue... This happens one after the other: they don't run their JavaScript in parallel: one function context gets restored, does its thing until the next await, and then the same happens with the other function context.
I have been reading up on methods to implement a polling function and found a great article on https://davidwalsh.name/javascript-polling. Now using a setTimeout rather than setInterval to poll makes a log of sense, especially with an API that I have no control over and has shown to have varying response times.
So I tried to implement such a solution in my own code in order to challenge my understanding of callbacks, promises and the event loop. I have followed guidance outlined in the post to avoid any anti-patterns Is this a "Deferred Antipattern"? and to ensure promise resolution before a .then() promise resolve before inner promise resolved and this is where I am getting stuck. I have put some code together to simulate the scenario so I can highlight the issues.
My hypothetical scenario is this:
I have an API call to a server which responds with a userID. I then use that userID to make a request to another database server which returns a set of data that carries out some machine learning processing that can take several minutes.
Due to the latency, the task is put onto a task queue and once it is complete it updates a NoSql database entry with from isComplete: false to isComplete: true. This means that we then need to poll the database every n seconds until we get a response indicating isComplete: true and then we cease the polling. I understand there are a number of solutions to polling an api but I have yet to see one involving promises, conditional polling, and not following some of the anti-patterns mentioned in the previously linked post. If I have missed anything and this is a repeat I do apologize in advance.
So far the process is outlined by the code below:
let state = false;
const userIdApi = () => {
return new Promise((res, rej) => {
console.log("userIdApi");
const userId = "uid123";
setTimeout(()=> res(userId), 2000)
})
}
const startProcessingTaskApi = userIdApi().then(result => {
return new Promise((res, rej) => {
console.log("startProcessingTaskApi");
const msg = "Task submitted";
setTimeout(()=> res(msg), 2000)
})
})
const pollDatabase = (userId) => {
return new Promise((res, rej) => {
console.log("Polling databse with " + userId)
setTimeout(()=> res(true), 2000)
})
}
Promise.all([userIdApi(), startProcessingTaskApi])
.then(([resultsuserIdApi, resultsStartProcessingTaskApi]) => {
const id = setTimeout(function poll(resultsuserIdApi){
console.log(resultsuserIdApi)
return pollDatabase(resultsuserIdApi)
.then(res=> {
state = res
if (state === true){
clearTimeout(id);
return;
}
setTimeout(poll, 2000, resultsuserIdApi);
})
},2000)
})
I have a question that relates to this code as it is failing to carry out the polling as I need:
I saw in the accepted answer of the post How do I access previous promise results in a .then() chain? that one should "Break the chain" to avoid huge chains of .then() statements. I followed the guidance and it seemed to do the trick (before adding the polling), however, when I console logged out every line it seems that userIdApi is executed twice; once where it is used in the startProcessingTaskApi definition and then in the Promise.all line.
Is this a known occurrence? It makes sense why it happens I am just wondering why this is fine to send two requests to execute the same promise, or if there is a way to perhaps prevent the first request from happening and restrict the function execution to the Promise.all statement?
I am fairly new to Javascript having come from Python so any pointers on where I may be missing some knowledge to be able to get this seemingly simple task working would be greatly appreciated.
I think you're almost there, it seems you're just struggling with the asynchronous nature of javascript. Using promises is definitely the way to go here and understanding how to chain them together is key to implementing your use case.
I would start by implementing a single method that wraps setTimeout to simplify things down.
function delay(millis) {
return new Promise((resolve) => setTimeout(resolve, millis));
}
Then you can re-implement the "API" methods using the delay function.
const userIdApi = () => {
return delay(2000).then(() => "uid123");
};
// Make userId an argument to this method (like pollDatabase) so we don't need to get it twice.
const startProcessingTaskApi = (userId) => {
return delay(2000).then(() => "Task submitted");
};
const pollDatabase = (userId) => {
return delay(2000).then(() => true);
};
You can continue polling the database by simply chaining another promise in the chain when your condition is not met.
function pollUntilComplete(userId) {
return pollDatabase(userId).then((result) => {
if (!result) {
// Result is not ready yet, chain another database polling promise.
return pollUntilComplete(userId);
}
});
}
Then you can put everything together to implement your use case.
userIdApi().then((userId) => {
// Add task processing to the promise chain.
return startProcessingTaskApi(userId).then(() => {
// Add database polling to the promise chain.
return pollUntilComplete(userId);
});
}).then(() => {
// Everything is done at this point.
console.log('done');
}).catch((err) => {
// An error occurred at some point in the promise chain.
console.error(err);
});
This becomes a lot easier if you're able to actually use the async and await keywords.
Using the same delay function as in Jake's answer:
async function doItAll(userID) {
await startTaskProcessingApi(userID);
while (true) {
if (await pollDatabase(userID)) break;
}
}
function sleep(ms) {
var start = new Date().getTime(),
expire = start + ms;
while (new Date().getTime() < expire) {}
return;
}
async function executeWithDelay(offers) {
return Promise.all(
offers.map((offer, i) =>
getDetailedInfo(offer).then(data => {
offer = data;
if (i % 5 === 0) {
console.log('executed but it delays now for 3 seconds');
sleep(3000);
}
})
)
).then(function(data) {
return offers;
});
}
Trying to achieve web-scraping with possible best solution available. I am combining cheerio and puppeteer together and i have some nice code to debug. The above code works fine if the offers data is less i.e 5-10 records. If it exceeds, the browser gets crashed, though i am running in headless version.
This is basically due to in-house bug from the package what i am using which is unable to handle the load. My experiment is to a little delay for every batch of records and complete the full records execution.
But with the below code, it's behaving as non-blocking code and first time it delays and rest is executed constantly.
Should i use for loop instead of map or is there any alternative to handle this situation?
You never want to "sleep" with a busy-wait loop like that. It means nothing else can be done on the main thread.
Once the operations the promises represent have been started, unless they provide some custom means of pausing them, you can't pause them. Remember, promises don't actually do anything; they provide a standard way to observe something being done.
Another problem with that code is that you're returning offers, not the results of calling getExtendedInfo.
If the problem is calling getExtendedInfo too often, you can insert a delay (using setTimeout, not a busy-loop). For instance, this does the first request to getExtendedInfo almost immediately, the next after one second, the next after two seconds, and so on:
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function executeWithDelay(offers) {
return Promise.all(
offers.map((offer, i) =>
sleep(i * 1000).then(getDetailedInfo)
)
});
}
Obviously, you can adjust that delay as required.
(Note that executeWithDelay isn't declared async; no need, since it has to use Promise.all anyway.)
So, I'm having a problem. I need to make potentially hundreds of http calls and they must be done one after the other. I also need to be able to interrupt the whole process at any time.
The solution I'm using right now is this one, but it doesn't allow me to properly interrupt the "chain" (I use jQuery and Deferreds because I haven't learned to replace them with Promises yet):
function doAnHttpCall() {
return $.post('http://the.domain/theendpoint', { theParam: this });
}
var anArrayOfParams = ['param1', 'param2', 'param3'];
var p = $.Deferred();
setTimeout(p.resolve.bind(p), 0);
var promise = anArrayOfParams.reduce(function(prev, cur) {
return prev.then(doAnHttpCall.bind(cur));
}, p)
.done(function() {
console.log('all done.');
});
(I've found this solution here)
The problem here is that the only way to sort-of break out of the reduce function is by modifying the array you're looping through and check for a particular value and always return immediately when you find it, instead of executing the "doAnHttpCall" method (in this case). This would still make me loop over potentially hundreds of elements of the array instead of just interrupting the process, which is very ugly.
There must be a better way to do this. Or do I really need to use a function that calls itself with the next element to process when an http call has finished? It sounds "bad-practice-y".
Thanks for the help.
You would use async/await:
const request = param => $.post('http://the.domain/theendpoint', { param });
(async () => {
for(const param of ['param1', 'param2', 'param3']) {
let result = await request(param);
if (result === 'particular value') break;
}
console.log('all done!');
})();