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();
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()
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!!!
Here is my code:
let cleanRoom = function() {
return new Promise(function(resolve, reject) {
resolve('Cleaned The Room');
});
};
let removeGarbage = function(message) {
return new Promise(function(resolve, reject) {
resolve(message + ' remove Garbage');
});
};
let winIcecream = function(message) {
return new Promise(function(resolve, reject) {
resolve( message + ' won Icecream');
});
};
Promise.all([cleanRoom, removeGarbage, winIcecream]). then(function(){
console.log('all finished');
})
Promise.all([cleanRoom(), removeGarbage(), winIcecream()]). then(function(){
console.log('all finished');
})
As you can see, both (with and without parentheses) return the same result. So is this right to say?
You should use parentheses only when you want to pass something to the function. Otherwise no need to use it.
This is the difference:
On the first Promise.all you are passing a reference to a function.
On the second Promise.all you are passing a reference to a
Promise.
If you want to log the result you will see the difference:
let cleanRoom = function() {
return new Promise(function(resolve, reject) {
resolve('Cleaned The Room');
});
};
let removeGarbage = function(message) {
return new Promise(function(resolve, reject) {
resolve(message + ' remove Garbage');
});
};
let winIcecream = function(message) {
return new Promise(function(resolve, reject) {
resolve( message + ' won Icecream');
});
};
Promise.all([cleanRoom, removeGarbage, winIcecream]).then(function(result){
console.log(result);
})
Promise.all([cleanRoom(), removeGarbage('foo'), winIcecream('bar')]).then(function(result){
console.log(result);
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
EDIT
I forgot to mention, in some of your functions you expect to get a message parameter.
Well, you can't pass it without using parentheses.
I have the following discovery code using the mdns-js package.
in ./lib/deviceDiscovery.js:
var mdns = require('mdns-js');
const browsers = new Map();
const INFINITE = -1;
function createABrowser(theServiceType, timeout) {
if (browsers.has(theServiceType)) {
return;
}
return new Promise(function(resolve, reject) {
var browser = mdns.createBrowser(theServiceType);
browser.on('ready', function() {
browsers.set(theServiceType, browser);
resolve(browser);
});
if (timeout != INFINITE) {
setTimeout(function onTimeout() {
try {
browser.stop();
browsers.delete(browser.serviceType);
} finally {
reject('browser ' + browser.toString() + ' timed out.');
}
}, timeout);
}
});
}
module.exports.startService = function(services, timeout) {
timeout = timeout || INFINITE;
promises = [];
services.forEach(function(service) {
promises.push(createABrowser(service, timeout));
});
return Promise.all(promises);
}
module.exports.stopService = function() {
browsers.values().forEach(function(browser) {
browser.stop();
});
browsers.clear();
}
module.exports.getDevices = function() {
if (browsers.size == 0) {
reject('service was stopped');
} else {
const promises = [];
for (let browser of browsers.values()) {
promises.push(new Promise(function(resolve, reject) {
try {
browser.discover();
browser.on('update', function(data) {
mfps = new Set();
const theAddresses = data.addresses;
theAddresses.forEach(function(element) {
mfps.add(element);
});
resolve(mfps);
});
} catch(err) {
reject(err);
}
}));
};
return Promise.all(promises).then(function(values) {
return new Set(values);
}, function(reason) {
return reason;
});
}
}
and use it in another file like this:
const DeviceDiscoveryService = require('./lib/deviceDiscovery');
var co = require('co');
co(function *service() {
yield DeviceDiscoveryService.startService([internetPrinter, pdlPrinter, unixPrinter], TIMEOUT);
yield DeviceDiscoveryService.getDevices();
}).catch(onerror);
function onerror(err) {
// log any uncaught errors
}
The problem is that the second yield hangs; it seems that the promise returned by getDevices function isn't resolved indefinitely, although I see that the individual promises are resolved.
startService uses a similar Promise.all(...) but it works ok.
Another related question is about the mdns-js: it seems that for each (input) service, the browser receives multiple updates.
But I resolve the promise for each browser after the first update event... do I need to wait for multiple updates and how?
Any hints will be greatly appreciated! Thanks.
I believe that you share update be returning a promises from createABrowser at ALL times (instead of returning undefined if the service already exists). Without returning a promise, I think Promise.all() won't resolve.
Instead, create a promise at the top and resolve if it the service exists already, and return THAT promise.
For the getDevices() call, you're running a reject without returning a promise there as well. Would this work?
module.exports.getDevices = function() {
if (browsers.size == 0) {
// Create a new promise, return it, and immediately reject
return new Promise(function(resolve, reject) { reject('service was stopped') };
// reject('service was stopped'); <- There wasn't a promise here
} else {
const promises = [];
for (let browser of browsers.values()) {
promises.push(new Promise(function(resolve, reject) {
try {
browser.discover();
browser.on('update', function(data) {
mfps = new Set();
const theAddresses = data.addresses;
theAddresses.forEach(function(element) {
mfps.add(element);
});
resolve(mfps);
});
} catch(err) {
reject(err);
}
}));
};
return Promise.all(promises).then(function(values) {
return new Set(values);
}, function(reason) {
return reason;
});
}
}
I'm in a situation where multiple parts of my app wants access to data. I would like the first caller to initiate a fetch from the server, whilst subsequent requests should wait for the data to be fetched. How do I do this with a Promise?
I've tried something along these lines, with no success:
var promise = null;
var fetchComplete = false;
var data = null;
function getData() {
if (fetchComplete) {
return new Promise(function(resolve, reject) {
resolve(data);
});
} else {
if (promise === null) {
promise = new Promise(function(resolve, reject) {
getDataFromServer(function(response) {
fetchComplete = true;
data = response;
});
});
}
else {
return promise;
}
}
};
You can use a closure like so:
function getDataPromise() {
return new Promise(function(resolve, reject) {
getDataFromServer(resolve);
});
}
var getDataCached = (function() {
var fetchPromise = null;
return function() {
if (!fetchPromise) {
// Fetch data here and populate fetchPromise with an actual promise.
fetchPromise = getDataPromise();
}
return fetchPromise;
};
})();
The first invokation will populate the fetchPromise closure variable, while the rest would simply return it.