I'm pulling data from 3 differents APIs, and I want to merge all these results into one array.
I guess the proper way to do this is to use Promises:
var function1 = new Promise((resolve, reject)=>{
...
resolve();
});
var function2 = new Promise((resolve, reject)=>{
...
resolve();
});
var function3 = new Promise((resolve, reject)=>{
...
resolve();
});
Promise.all([function1, function2, function3]).then(function(values){
// Values are all here!
});
How can I call all the promises again and join them via Promise.all every second?
I've tried
setInterval(function(){
Promise.all([function1, function2, function3]).then(function(values){
// Values are all here and up to date!
});
}, 1000)
without success.
Thanks for your help!
You need to recreate the Promise objects every time you want to invoke them:
var function1 = (resolve, reject)=>{
console.log('calling 1');
resolve();
};
var function2 = (resolve, reject)=>{
console.log('calling 2');
resolve();
};
var function3 = (resolve, reject)=>{
console.log('calling 3');
resolve();
};
setInterval(function(){
Promise.all([new Promise(function1), new Promise(function2), new Promise(function3)]).then(function(values){
console.log('alldone');
});
}, 1000)
This is because the promise is only executed upon creation, and otherwise in your loop you're just attaching a new then() method which will not call your API.
EDIT:
Be advised that setInterval, as shown, will fire three requests to your API every 1 second. That's a pretty fast rate of fire and is likely to get you in trouble unless both your API and the network are blazing fast.
A more sensible approach might be to only fire the next request once the previous one has been handled.
To do that, simply substitute the setInterval call with this:
var callback = function(){
Promise.all([new Promise(function1), new Promise(function2), new Promise(function3)]).then(function(values){
console.log('all done');
setTimeout(callback, 1000);
console.log('next call successfully enqued');
});
};
setTimeout(callback, 1000);
Thanks to Kevin B for pointing this out.
Make sure you call the API each time (by creating new Promise).
/**
* Create function that return a NEW Promise each time.
* Once resolved a promise won't change state
*/
const function1 = () => new Promise((resolve, reject)=>{
// something
resolve('1 - ' + new Date());
});
const function2 = () => new Promise((resolve, reject)=>{
// something
resolve('2 - ' + new Date());
});
const function3 = () => new Promise((resolve, reject)=>{
// something
resolve('3 - ' + new Date());
});
/**
* For the setInterval callback, create a function
* that will return a new Promise from all the previous promises
*/
const all = () => Promise.all([
function1(),
function2(),
function3()
]).then(function(values){
console.log(values);
return values;
});
setInterval(all, 1000);
Answer
This question was in my interview and I had trouble that it should be implemented only using class, maybe you will find it useful for you and rewrite it using functions.
class HTTPService {
constructor(base = "", strategy = "default" | "queue", promises = []) {
this.base = base;
this.strategy = strategy;
this.promises = 0;
}
urlSerializer(payload) {
return `?${Object.entries(payload)
.map((el) => el.join("="))
.join("$")}`;
}
returnDefaultPromise(path) {
return new Promise((resolve) =>
setTimeout(() => {
resolve(path);
}, 1000)
);
}
returnQueuePromise(path) {
return new Promise((resolve) =>
setTimeout(() => {
this.promises -= 1000;
resolve(path);
}, this.promises)
);
}
get(url, payload) {
let serialized = payload ? this.urlSerializer(payload) : "";
if (!url) throw new Error("Please add url to function argument");
switch (this.strategy) {
case "default":
return this.returnDefaultPromise(`${this.base}/${url}${serialized}`);
case "queue":
this.promises += 1000;
return this.returnQueuePromise(`${this.base}/${url}${serialized}`);
default:
return new Promise((resolve) =>
resolve(`${this.base}/${url}${serialized}`)
);
}
}
}
const httpService = new HTTPService("http://test.com", "queue");
const httpService2 = new HTTPService("http://test.com", "default");
const br = document.createElement('br');
let div = document.createElement('div');
let content = document.createTextNode('');
httpService.get("/api/me").then((data) => {
content = document.createTextNode(data);
div.appendChild(content);
div.appendChild(br);
console.log(data);
});
// should be 'http://test.com/api/me'
// B:
httpService.get("/api/test", { foo: 1, test: 2 }).then((data) => {
content = document.createTextNode(data);
div.appendChild(content);
div.appendChild(br);
console.log(data);
// should be 'http://test.com/api/test?foo=1&test=2'
});
// C:
httpService.get("/api/test", { baz: 10, case: "some" }).then((data) => {
content = document.createTextNode(data);
div.appendChild(content);
div.appendChild(br);
console.log(data);
// should be 'http://test.com//api/test?baz=10$case=some'
});
// D:
httpService.get("/api/test", { bar: 1, dummy: "text" }).then((data) => {
content = document.createTextNode(data);
div.appendChild(content);
div.appendChild(br);
console.log(data);
// should be 'http://test.com//api/test?bar=1$dummy=text'
});
httpService2.get("/app/test").then((data) => {
content = document.createTextNode(data);
div.appendChild(br);
div.appendChild(content);
div.appendChild(br);
console.log(data);
});
document.querySelector('#result').appendChild(div)
<div id="result"></div>
This example to call functions
Also you can check analogy in react application through codesandbox
An alternative to setInterval() is using setTimeout() inside a recursive loop:
function joinedResults() {
Promise.all([function1, function2, function3])
.then(function(values){
console.log(values);
setTimeout(()=>{ joinedResults() }, 1000);
});
}
joinedResults();
This stackoverflow post has a similar question.
In order to chain your promise outside of setInterval, you can wrap it in a function:
let repeat = (ms, func) => new Promise(r => (setInterval(func, ms), wait(ms).then(r)));
repeat(1000, () => Promise.all([myfunction()])
.then(...)
Solution
Every time you need to create promises again and resolve them.
setInterval(function(){
var function1 = new Promise((resolve, reject)=>{
resolve(new Date());
});
var function2 = new Promise((resolve, reject)=>{
resolve(2);
});
var function3 = new Promise((resolve, reject)=>{
resolve(3);
});
Promise.all([function1, function2, function3]).then(function(values){
console.log(values);
});
}, 1000)
Run the above code its working!!!
Related
I have multiple scenarios where, based on a condition, I need to do asynchronous processing and later proceed regardless of the path taken. This code works as I expect:
let processingComplete = new Promise(function (resolve, reject) { }); // create pending promise
let condition = true;
var wait = function () {
return new Promise((resolve, reject) => {
setTimeout(resolve, 500);
});
}
if (condition) {
processingComplete = wait();
} else {
// do something else, synchronously
processingComplete = Promise.resolve();
}
processingComplete.then(() => {
console.log("entering processingComplete.then...")
});
However, if the promises are nested more than one deep the .then clause never fires. For example,
let processingComplete = new Promise(function (resolve, reject) { }); // create pending promise
let condition = true;
var wait = function () {
return new Promise((resolve, reject) => {
setTimeout(resolve, 500);
});
}
if (condition) {
wait()
.then(() => {
processingComplete = wait() // nesting of promises
})
} else {
// do something else, synchronously
processingComplete = Promise.resolve();
}
processingComplete.then(() => {
// this code never fires with nested promises
console.log("entering processingComplete.then...")
});
I'm certainly familiar with using promises, but I'm not understanding why this does't work. I'd welcome any insights.
Your second one just assigns processingComplete too late inside a .then() handler that gets called later AFTER you try to use it. You need to change your promises properly:
processingComplete = wait().then(wait);
Or, if there's other processing in the real code:
processingComplete = wait().then(() => {
// ... other code here
return wait();
});
Dealing with lots of promises is sometimes better done with async / await. In your example, you are assigning processingComplete a value after you called processingComplete.then(...). This might help:
let processingComplete = new Promise(function (resolve, reject) { });
let condition = true;
var wait = function () {
return new Promise((resolve, reject) => {
setTimeout(resolve, 500);
});
}
async function run() {
if (condition) {
await wait()
processingComplete = wait()
} else {
processingComplete = Promise.resolve();
}
processingComplete.then(() => {
console.log("entering processingComplete.then...")
});
}
run()
Anyone know why this does not run synchronically? The last promise seems to resolve before the first
...
var promise = Promise.resolve();
promise.then( () => {
return new Promise((resolve, reject) => {
var file1 = fileChooserCSV.files[0];
var reader1 = new FileReader();
reader1.onload = function(){
var csv = reader1.result;
csvJson = csvJSON(csv);
resolve();
};
reader1.readAsText(file1);
});
});
promise.then( () => {
return new Promise((resolve, reject) => {
var file2 = fileChooserConfig.files[0];
var reader2 = new FileReader();
reader2.onload = function(){
var config = reader2.result;
configJson = JSON.parse(config);
resolve();
};
reader2.readAsText(file2);
});
});
promise.then( () => {
return new Promise((resolve, reject) => {
console.log('end');
resolve();
});
});
The reader onload methods never seem to execute though they really should (there is data passed to them), and did so before they were moved to the promise. As the onload doesn't run the resolve() never fires ether to go onto the next then(), but the last then() does execute...
This code runs in a chrome extension popup if that makes any difference?
Many thanks!
UPDATE..
Restructuring it in a classic nested way works fine
var file1 = fileChooserCSV.files[0];
var reader1 = new FileReader();
reader1.onload = function(){
var csv = reader1.result;
csvJson = csvJSON(csv);
var file2 = fileChooserConfig.files[0];
var reader2 = new FileReader();
reader2.onload = function(){
var config = reader2.result;
configJson = JSON.parse(config);
console.log('end');
};
reader2.readAsText(file2);
};
reader1.readAsText(file1);
did u tryied Promise.All().then... like that
var promise = Promise.resolve(3);
Promise.all([true, promise])
.then(function(values) {
console.log(values); // [true, 3]
});
https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
In JS runtime the 'then' are moved to another stack and are executed only when the call stack is empty.
For example, here you create I create a Promise.resolve() like you. But notice it's then is executed only after the entire code was executed. You will see on you screen '53' instead of expected '35':
const promise = Promise.resolve(3)
promise.then(res => {
document.write(res)
})
document.write(5)
'then' is stored in additional stack and executed only later.
For more info take a look at this perfect explanation form Barak Chemo. Watch till 30:40.
Hope it helps
I'm learning about Promise's and have a little doubt assuming that I want to get resolved status out of Promises
and not want reject! Can I just call back the promise function inside
catch to make sure that I get only approved value! Is that possible or
will it throw an error or goes to loop iteration
let promisetocleantheroom = new Promise(function cleanroom(resolve, reject) {
//code to clean the room
//then as a result the clean variable will have true or flase
if (clean == "true") {
resolve("cleaned");
} else {
reject("not cleaned");
}
});
promisetocleantheroom.then(function cleanroom(fromResolve) {
// wait for the function to finish only then it would run the function then
console.log("the room is " + fromResolve);
}).catch(function cleanroom(fromReject) {
//calling back the promise again
cleanroom();
});
If you don't mind having higher order functions and recursivity, here is my proposed solution.
First you need to wrap your promise in a function to recreate it when it fails. Then you can pass it to retryPromiseMaker with a partial error handler to create another function that will act as retrier. And this function will return a Promise that will fulfill only if one of the inner promises fulfills.
Sounds complicated but I promise you it is not!
const retryPromiseMaker = (fn, errorfn = null) => {
const retryPromise = (retries = 3, err = null) => {
if (err) {
errorfn(err);
}
if (retries === 0) {
return Promise.reject(err);
}
return fn()
.catch(err => retryPromise(retries - 1, err));
};
return retryPromise;
}
const cleanTheRoom = (resolve, reject) => {
// simulate cleaning as a probability of 33%
const clean = Math.random() < 0.33;
setTimeout(() => {
if (clean) {
resolve("cleaned");
} else {
reject("not cleaned");
}
}, Math.random() * 700 + 200);
};
const promiseToCleanTheRoom = () => new Promise(cleanTheRoom);
const logStatus = end => value => {
let text = '';
if (end){
text += "at the end ";
}
text += "the room is " + value;
console.log(text);
};
retryPromiseMaker(promiseToCleanTheRoom, logStatus(false))(4)
.then(logStatus(true),logStatus(true));
I want to test how much requests i can do and get their total time elapsed. My Promise function
async execQuery(response, query) {
let request = new SQL.Request();
return new Promise((resolve, reject) => {
request.query(query, (error, result) => {
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
}
And my api
app.get('/api/bookings/:uid', (req, res) => {
let st = new stopwatch();
let id = req.params.uid;
let query = `SELECT * FROM booking.TransactionDetails WHERE UID='${id}'`;
for (let i = 0; i < 10000; i++) {
st.start();
db.execQuery(res, query);
}
});
I can't stop the for loop since its async but I also don't know how can I stop executing other calls after the one which first rejects so i can get the counter and the elapsed time of all successful promises. How can i achieve that?
You can easily create a composable wrapper for this, or a subclass:
Inheritance:
class TimedPromise extends Promise {
constructor(executor) {
this.startTime = performance.now(); // or Date.now
super(executor);
let end = () => this.endTime = performance.now();
this.then(end, end); // replace with finally when available
}
get time() {
return this.startTime - this.endTime; // time in milliseconds it took
}
}
Then you can use methods like:
TimedPromise.all(promises);
TimedPromise.race(promises);
var foo = new TimedPromise(resolve => setTimeout(resolve, 100);
let res = await foo;
console.log(foo.time); // how long foo took
Plus then chaining would work, async functions won't (since they always return native promises).
Composition:
function time(promise) {
var startTime = performance.now(), endTime;
let end = () => endTime = performance.now();
promise.then(end, end); // replace with finally when appropriate.
return () => startTime - endTime;
}
Then usage is:
var foo = new Promise(resolve => setTimeout(resolve, 100);
var timed = time(foo);
await foo;
console.log(timed()); // how long foo took
This has the advantage of working everywhere, but the disadvantage of manually having to time every promise. I prefer this approach for its explicitness and arguably nicer design.
As a caveat, since a rejection handler is attached, you have to be 100% sure you're adding your own .catch or then handler since otherwise the error will not log to the console.
Wouldn't this work in your promise ?
new Promise((resolve, reject) => {
var time = Date.now();
request.query(query, (error, result) => {
if (error) {
reject(error);
} else {
resolve(result);
}
});
}).then(function(r){
//code
}).catch(function(e){
console.log('it took : ', Date.now() - time);
});
Or put the .then and .catch after your db.execQuery() call
You made 2 comments that would indicate you want to stop all on going queries when a promise fails but fail to mention what SQL is and if request.query is something that you can cancel.
In your for loop you already ran all the request.query statements, if you want to run only one query and then the other you have to do request.query(query).then(-=>request.query(query)).then... but it'll take longer because you don't start them all at once.
Here is code that would tell you how long all the queries took but I think you should tell us what SQL is so we could figure out how to set connection pooling and caching (probably the biggest performance gainer).
//removed the async, this function does not await anything
// so there is no need for async
//removed initializing request, you can re use the one created in
// the run function, that may shave some time off total runtime
// but not sure if request can share connections (in that case)
// it's better to create a couple and pass them along as their
// connection becomes available (connection pooling)
const execQuery = (response, query, request) =>
new Promise(
(resolve, reject) =>
request.query(
query
,(error, result) =>
(error)
? reject(error)
: resolve(result)
)
);
// save failed queries and resolve them with Fail object
const Fail = function(detail){this.detail=detail;};
// let request = new SQL.Request();
const run = (numberOfTimes) => {
const start = new Date().getTime();
const request = new SQL.Request();
Promise.all(
(x=>{
for (let i = 0; i < numberOfTimes; i++) {
let query = `SELECT * FROM booking.TransactionDetails WHERE UID='${i}'`;
db.execQuery(res, query, request)
.then(
x=>[x,query]
,err=>[err,query]
)
}
})()//IIFE creating array of promises
)
.then(
results => {
const totalRuntime = new Date().getTime()-start;
const failed = results.filter(r=>(r&&r.constructor)===Fail);
console.log(`Total runtime in ms:${totalRuntime}
Failed:${failed.length}
Succeeded:${results.length-failed.length}`);
}
)
};
//start the whole thing with:
run(10000);
So for example, lets say I have this code:
var cmd = require('node-cmd')
function getStuff() {
return new Promise((resolve, reject) => {
var dataNStuff;
cmd.get('brew --version', data => {
dataNStuff += data;
})
cmd.get('yarn global ls', data => {
dataNStuff += data;
})
resolve(dataNStuff)
})
}
In this case cmd.get() is async, so I don't know when the data is coming in. I want to be able to have both calls already have the data come in before I resolve(dataNStuff), is this even possible with a Promise, and no I do not want to use a callback in this scenario. Is there a much simpler or faster way of doing the exact same thing?
Using Promises for the solution, use Promise.all, and "promisified" version of cmd.get
var cmd = require('node-cmd');
var cmdPromise = arg => new Promise((resolve, reject) => cmd.get(arg, resolve));
function getStuff() {
return Promise.all([cmdPromise('brew --version'), cmdPromise('yarn global ls')])
.then(results => results.join(''));
}
to "explain" cmdPromise in case that compact version isn't readable, it's basically this:
var cmdPromise = function cmdPromise(arg) {
return new Promise((resolve, reject) => {
cmd.get(arg, data => resolve(data));
});
};
Here's a straightforward solution involving Promises.
function getStuff() {
var dataNStuff = '';
var promiseOne = new Promise(function(resolve, reject) {
cmd.get('brew --version', data => {
dataNStuff += data;
resolve();
});
});
var promiseTwo = new Promise(function(resolve, reject) {
cmd.get('yarn global ls', data => {
dataNStuff += data;
resolve();
});
});
return Promise.all([promiseOne, promiseTwo]).then(function() {
return dataNStuff;
});
}
I assume that cmd.get will execute one after the other. If this assumption is incorrect, then there is no guarantee of the order of the strings. (ie It may display brew before yarn or it may display yarn before brew.)
ES2017 answer: removing the .then() in favor of = via async/await (as the comment from #stephen-bugs-kamenar mentions.
var log = console.log;
var getBanana = function() {
return new Promise(function(resolve, reject) {
setTimeout(function(){
resolve('banana')
}, 2000);
});
}
var getGrape = function() {
return new Promise(function(resolve, reject) {
setTimeout(function(){
resolve('grape')
}, 3000);
});
}
var start = async function(){
var result = await Promise.all([getBanana(), getGrape(), 'apple'])
console.log('All done', result)
}
start();