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()
Related
I have a situation where a bunch of functions are needing to wait for a promise to settle because it's the init function;
self.init=new Promise(function(resolve){
//do stuff, take awhile
resolve();
});
But, while it's init'ing, the async nature means other functions that depend on it being init are being called. I want those functions to wait for the init to finish, then continue.
I tried doing this inside each function
function doSomethingUseful(){
self.init.reflect().then(function () {
//do functions purpose
});
}
function doSomethingUseless(){
self.init.reflect().then(function () {
//do functions purpose
});
}
But it only works randomly, probably only works if init has settled, and if it hasn't, it just hangs here, weirdly hangs the whole app, despite it being async.
I am trying to replace a former solution that involved intervals and checking a Boolean isInit in each function call.
Is there a bluebird function to do this? Or another way to keep waiting and checking on a promise to see if it is resolved?
The app has this sort of structure in a number of places. Usually around sqlite read/writes. An init to open the database, but while it's opening, the page is loading and it's already trying to read/write to the tables, so those read/writes are forced to wait by using setInterval and repeatedly checking to see if the init has finished.
Here's an example using google analytics.
function Analytics() {
var self = this;
self.ready = ko.observable(false).subscribeTo('application:ready'); //attached to page ready event in jquerymobile and cordova
self.trackerInit = new Promise(function (resolve, reject) {
ko.computed(function () {
if (self.ready()) {
window.ga.startTrackerWithId('id', 1000, resolve, reject);
}
});
});
}
Analytics.prototype.trackSpeed = function (cat, interval, variable, label) {
var self = this;
console.log("speed tracker", cat, interval, variable, label); //this logs
return self.trackerInit.then(function () {
console.log("speed tracker confirm init"); //this never logs, all execution stops including other async code
return new Promise(function (resolve, reject) {
window.ga.trackTiming(cat, interval, variable, label, resolve, reject);
});
}).catch(function (e) {
if (e.message === "send timeout") {
return true; //who cares about timeouts anyways
} else {
throw e;//rethrow it
}
});
};
Function is called within page change event without a return, purely async. Calling it causes all execution to stop.
The ready ko is done like this
self.ready = ko.observable(false).publishOn('application:ready');
var deviceReady = new Promise(function (resolve) {
$(document).on('deviceready', resolve);
});
var pageReady = new Promise(function (resolve) {
$(document).on('pagecreate', resolve);
});
Promise.all([deviceReady, pageReady]).then(function () {
//a couple of page of code and...
self.ready(true);
});
Changing the init like this produces the same result of a hang when checking it's results
self.trackerInit = new Promise(function (resolve, reject) {
console.log("initting");
checker = setInterval(function () {
if (window.ga) {
console.log("ready init");
window.ga.startTrackerWithId('id', 100, function(){
clearInterval(checker);
console.log("init complete");
resolve();
}, reject);
}
}, 1000);
});
They are just promises. Just use then to chain them
function doSomethingUseful() {
// wait for init to finish, then do our stuff
// return the new chained promise in case someone wants to wait on us
return self.init.then(function () {
// do stuff
});
}
function doSomethingUseless() {
// wait for init to finish, then do our stuff
// return the new chained promise in case someone wants to wait on us
return self.init.then(function () {
// do stuff
});
}
// do both of those things and then do something else!
Promise.all([doSomethingUseful(), doSomethingUseless()]).then(function () {
console.log("init is done. And we've done something useful and useless.")
}
Edit:
Based on your additional code, the problem is that if the application is "ready" before your Analytics component is constructed, then you will never receive the "application:ready" (because it came before you subscribed) so your "ready" observable will remain false. According to the postbox docs, you need to pass true as a second argument to subscribeTo so that you'll get the ready value even if it occurred in the past:
ko.observable(false).subscribeTo("application:ready", true)
However, constructing all of these observables and computeds just to feed into a promise is overkill. How about:
self.trackerInit = new Promise(function (resolve, reject) {
const s = ko.postbox.subscribe("application:ready", function (value) {
if (value) {
s.dispose(); // stop listening (prevent memory leak
window.ga.startTrackerWithId('id', 1000, resolve, reject);
}
}, true);
});
You can even turn this into a promise helper:
function whenReady(eventName) {
return new Promise((resolve, reject) => {
const s = ko.postbox.subscribe(eventName, value => {
if (ready) {
s.dispose();
resolve(value);
}
}, true);
});
}
function startGaTracker(id, timeout) {
return new Promise((resolve, reject) => window.ga.startTrackerWithId(id, timeout, resolve, reject);
}
Then you can write:
self.trackerInit = whenReady("application:ready")
.then(() => startGaTracker("id", 100));
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!!!
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 tackling a project that requires me to use JavaScript with an API method call. I'm a Java programmer who has never done web development before so I'm having some trouble with it.
This API method is asynchronous and it's in a while loop. If it returns an empty array, the while loop finishes. Otherwise, it loops. Code:
var done = true;
do
{
async_api_call(
"method.name",
{
// Do stuff.
},
function(result)
{
if(result.error())
{
console.error(result.error());
}
else
{
// Sets the boolean to true if the returned array is empty, or false otherwise.
done = (result.data().length === 0) ? true : false;
}
}
);
} while (!done);
This doesn't work. The loop ends before the value of "done" is updated. I've done some reading up on the subject and it appears I need to use promises or callbacks because the API call is asynchronous, but I can't understand how to apply them to the code I have above.
Help would be appreciated!
edit: see the bottom, there is the real answer.
I encourage you yo use the Promise API. Your problem can be solved using a Promise.all call:
let promises = [];
while(something){
promises.push(new Promise((r, j) => {
YourAsyncCall(() => r());
});
}
//Then this returns a promise that will resolve when ALL are so.
Promise.all(promises).then(() => {
//All operations done
});
The syntax is in es6, here is the es5 equivalent (Promise API may be included externally):
var promises = [];
while(something){
promises.push(new Promise(function(r, j){
YourAsyncCall(function(){ r(); });
});
}
//Then this returns a promise that will resolve when ALL are so.
Promise.all(promises).then(function(){
//All operations done
});
You can also make your api call return the promise and push it directly to the promise array.
If you don't want to edit the api_call_method you can always wrap your code in a new promise and call the method resolve when it finishes.
edit: I have seen now the point of your code, sorry. I've just realized that Promise.all will not solve the problem.
You shall put what you posted (excluding the while loop and the control value) inside a function, and depending on the condition calling it again.
Then, all can be wraped inside a promise in order to make the external code aware of this asynchronous execution. I'll post some sample code later with my PC.
So the good answer
You can use a promise to control the flow of your application and use recursion instead of the while loop:
function asyncOp(resolve, reject) {
//If you're using NodeJS you can use Es6 syntax:
async_api_call("method.name", {}, (result) => {
if(result.error()) {
console.error(result.error());
reject(result.error()); //You can reject the promise, this is optional.
} else {
//If your operation succeeds, resolve the promise and don't call again.
if (result.data().length === 0) {
asyncOp(resolve); //Try again
} else {
resolve(result); //Resolve the promise, pass the result.
}
}
});
}
new Promise((r, j) => {
asyncOp(r, j);
}).then((result) => {
//This will call if your algorithm succeeds!
});
/*
* Please note that "(...) => {}" equivals to "function(...){}"
*/
sigmasoldier's solution is correct, just wanted to share the ES6 version with async / await:
const asyncFunction = (t) => new Promise(resolve => setTimeout(resolve, t));
const getData = async (resolve, reject, count) => {
console.log('waiting');
await asyncFunction(3000);
console.log('finshed waiting');
count++;
if (count < 2) {
getData(resolve, reject, count);
} else {
return resolve();
}
}
const runScript = async () => {
await new Promise((r, j) => getData(r, j, 0));
console.log('finished');
};
runScript();
If you don't want to use recursion you can change your while loop into a for of loop and use a generator function for maintaining done state. Here's a simple example where the for of loop will wait for the async function until we've had 5 iterations and then done is flipped to true. You should be able to update this concept to set your done variable to true when your webservice calls have buffered all of your data rows.
let done = false;
let count = 0;
const whileGenerator = function* () {
while (!done) {
yield count;
}
};
const asyncFunction = async function(){
await new Promise(resolve => { setTimeout(resolve); });
};
const main = new Promise(async (resolve)=>{
for (let i of whileGenerator()){
console.log(i);
await asyncFunction();
count++;
if (count === 5){
done = true;
}
}
resolve();
});
main.then(()=>{
console.log('all done!');
});
Also you may try recursion solution.
function asyncCall(cb) {
// Some async operation
}
function responseHandler(result) {
if (result.error()) {
console.error(result.error());
} else if(result.data() && result.data().length) {
asyncCall(responseHandler);
}
}
asyncCall(responseHandler);
Here is a solution I came up with. Place this in an async function.
let finished = false;
const loop = async () => {
return new Promise(async (resolve, reject) => {
const inner = async () => {
if (!finished) {
//insert loop code here
if (xxx is done) { //insert this in your loop code after task is complete
finshed = true;
resolve();
} else {
return inner();
}
}
}
await inner();
})
}
await loop();
If you don't want to use Promises you can restructure your code like so:
var tasks = [];
var index = 0;
function processNextTask()
{
if(++index == tasks.length)
{
// no more tasks
return;
}
async_api_call(
"method.name",
{
// Do stuff.
},
function(result)
{
if(result.error())
{
console.error(result.error());
}
else
{
// process data
setTimeout(processNextTask);
}
}
);
}
Your loop won't work, because it is sync, your async task is async, so the loop will finish before the async task can even respond. I'd reccomend you to use Promises to manage async tasks:
//first wrapping your API into a promise
var async_api_call_promise = function(methodName, someObject){
return new Promise((resolve, reject) => {
async_api_call(methodName, someObject, function(result){
if(result.error()){
reject( result.error() )
}else{
resolve( result.data() )
}
});
})
}
now to your polling code:
//a local utility because I don't want to repeat myself
var poll = () => async_api_call_promise("method.name", {/*Do stuff.*/});
//your pulling operation
poll().then(
data => data.length === 0 || poll(), //true || tryAgain
err => {
console.error(err);
return poll();
}
).then((done) => {
//done === true
//here you put the code that has to wait for your "loop" to finish
});
Why Promises? Because they do state-management of async operations. Why implement that yourself?
let taskPool = new Promise(function(resolve, reject) {
resolve("Success!");
});
let that = this;
while (index < this.totalPieces) {
end = start + thisPartSize;
if (end > filesize) {
end = filesize;
thisPartSize = filesize - start;
}
taskPool.then(() => {
that.worker(start, end, index, thisPartSize);
});
index++;
start = end;
}
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();