I ran into a bug in my code that puzzled me for a long time and am looking for some clarification.
In this code, the commented out inner promise was causing a problem. The Promise.all() at the end was continuing as soon as the setTimeout hit, not after the resolve inside the timeout.
Wrapping the async code with a promise fixes the flow problem, but why is this?
Essentially, why can't we just run normal async code in a .then() chain, an return a Promise.resolve() at the end of the async callback?
var asyncPromise = function() {
return new Promise(function(resolve, reject) {
setTimeout(function() {
console.log('Async Promise done');
resolve();
}, 1000);
});
};
var generateSignupPromises = function(qty) {
var promiseArray = [];
for (var i = 1; i <= qty; i++) {
promiseArray.push(
function() {
return asyncPromise()
.then(function() {
console.log('Before Timeout');
//Uncommenting this fixes the issue
//return new Promise(function(resolve, reject) {
setTimeout(function() {
console.log('After Timeout');
//resolve();
return Promise.resolve();
}, 500);
//})
});
}
);
}
return promiseArray;
};
var test = generateSignupPromises(1);
Promise.all([test[0]()])
.then(function() {
console.log('Done');
});
Link to running code: http://www.es6fiddle.net/imfdtuxc/
why can't we just run normal async code in a .then() chain, an return a Promise.resolve() at the end of the async callback?
You perfectly can. But any value - be it a promise or whatever - being returned from a plain asynchronous callback is just ignored as usual.
There is nothing that starting the asynchronous action inside a then callback changes about this, setTimeout just doesn't return a promise - and the then won't know about anything asynchronous happening that it could wait for.
If you want to return a promise from a callback and get another promise for that eventual result, it has to be a then callback:
asyncPromise()
.then(function() {
return new Promise(function(resolve, reject) {
// ^^^^^^
setTimeout(resolve, 500);
}).then(function() {
// ^^^^^^^^^^^^^^^
return Promise.resolve();
});
});
Then is a sync function so if you want to do async task inside then, you has to return a Promise.
Also, Promise.all expect an array of promises. Not an array of an array.
var asyncPromise = function() {
return new Promise(function(resolve, reject) {
setTimeout(function() {
console.log('Async Promise done');
resolve();
}, 1000);
});
};
var generateSignupPromises = function(qty) {
var promiseArray = [];
for (var i = 1; i <= qty; i++) {
promiseArray.push(
function() {
return asyncPromise()
.then(function() {
console.log('Before Timeout');
//Uncommenting this fixes the issue
return new Promise(function(resolve, reject) {
setTimeout(function() {
console.log('After Timeout');
resolve();
//return Promise.resolve();
}, 500);
})
});
}
);
}
return promiseArray;
};
var test = generateSignupPromises(1);
Promise.all([test[0]()])
.then(function() {
console.log('Done');
});
http://www.es6fiddle.net/imfe2sze/
Related
// read data with progress
function nprogressWrapPromise(fn, hide = false) {
if(hide) {
// no progress
return fn()
}
// have progress
const done = nprogressStartLoad();
return fn().then(() => {
done();
});
}
function print(message) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
console.log(message);
resolve();
}, 1000);
});
}
call nprogressWrapPromise multiple times in different places
nprogressWrapPromise(async () => {
return await print("one")
})
nprogressWrapPromise(async () => {
return await print("two")
})
nprogressWrapPromise(async () => {
return await print("three")
})
I use the NProgress to when I ask the promise ,the page is display the propress
let started = 0;
function nprogressStartLoad() {
if (started == 0) {
window.NProgress.start()
}
started++;
return () => {
started--;
if (started <= 0) {
started = 0;
window.NProgress.done();
}
}
}
I want to add the time, in the five seconds, only the first promise have process, the others promise dont have process.
You can do it like this:
let promise = undefined;
function print(message) {
if (promise) return promise;
return promise = new Promise(function (resolve, reject) {
setTimeout(function () {
console.log(message);
resolve();
promise = undefined;
}, 1000);
});
}
So, we use promise as a semaphor. Whenever a new promise is created, we simply store it into the variable called promise and we will proceed reusing this variable instead of creating a promise for references even while the promise is not resolved yet. Once the promise is resolved, we set promise to undefined, so at the next call of print we will have a new Promise.
EDIT
Similarly to the semaphore above, you can use another semaphore for your other function:
let npprogressWrapPromiseDone = undefined;
// read data with progress
function nprogressWrapPromise(fn, hide = false) {
if(hide) {
// no progress
return fn()
}
if (npprogressWrapPromiseDone) return npprogressWrapPromiseDone;
// have progress
const done = nprogressStartLoad();
return npprogressWrapPromiseDone = fn().then(() => {
done();
});
}
What's the difference between the two (without a return)
function doAsyncTask() {
var promise = new Promise((resolve, reject) => {
setTimeout(() => {
console.log("Async Work Complete");
if (error) {
reject();
} else {
resolve();
}
}, 1000);
});
return promise;
}
The following has no "Return Promise"
function doAsyncTask() {
var promise = new Promise((resolve, reject) => {
setTimeout(() => {
console.log("Async Work Complete");
if (error) {
reject();
} else {
resolve();
}
}, 1000);
});
}
As an extension to Quentin's answer, you should return a promise always.
Idea is, if a function is an async function, it should provide a way to listen to changes. Its upon caller to decide if they need to react to changes.
So you can call your function as:
doAsyncTask().then(...);
or just
doAsyncTask();
But if we do not return promise, caller will never have an option to llisten.
ES6 why does one have to return a promise?
You don't.
What's the difference between the two (without a return)
The difference is that one doesn't return anything.
(So you can't call doAsyncTask and make use of the return value).
var doAsyncTask1 = function() {
var promise = new Promise(resolve => {
/// task that takes 5 seconds
setTimeout(resolve, 5000);
});
return promise;
}
var doAsyncTask2 = function() {
var promise = new Promise(resolve => {
/// task that takes 5 seconds
setTimeout(resolve, 5000);
});
// no return
}
await doAsyncTask1();
console.log('task complete'); // this line of code runs 5 seconds after the previous one
await doAsyncTask2(); // because you have returned nothing, 'await' does nothing
console.log('task2 not complete yet'); // this line of code runs immediately after the previous one, before the 5-second task is complete
I have been trying to understand the architecture behind a promise call in javscript and here is something that i assumed is happening behind the scene
function Promise() {
this.stack = [];
this.then = function(fn) {
this.stack.push(fn);
return this;
}
this.resolve = function(data) {
if (this.stack.length) {
var cb = this.stack[0];
this.stack.shift();
cb.call(null, {
resolve: this.resolve.bind(this)
}, data);
}
}
}
// --- below is a working implementation --- //
var bar = function() {
var promise = new Promise();
setTimeout(function() {
console.log("1");
promise.resolve();
}, 2000);
return promise;
}
bar().then(function(p) {
setTimeout(function() {
console.log("2");
p.resolve();
}, 1000);
}).then(function(p) {
setTimeout(function() {
console.log("3");
p.resolve();
}, 500);
}).then(function(p) {
setTimeout(function() {
console.log("4");
p.resolve();
}, 300);
});
in my library, after every resolve call, i am calling the next callback in the stack and shifting the array by one
however in other libraries that i have linked, every-time the first promise is resolved a loop is being run through out the entire array while it keeps calling the callback stacks.
D.js
function execCallbacks() {
/*jshint bitwise:false*/
if (status === 0) {
return;
}
var cbs = pendings,
i = 0,
l = cbs.length,
cbIndex = ~status ? 0 : 1,
cb;
pendings = [];
for (; i < l; i++) {
(cb = cbs[i][cbIndex]) && cb(value);
}
}
tiny Promise.js
_complete: function(which, arg) {
// switch over to sync then()
this.then = which === 'resolve' ?
function(resolve, reject) {
resolve && resolve(arg);
} :
function(resolve, reject) {
reject && reject(arg);
};
// disallow multiple calls to resolve or reject
this.resolve = this.reject =
function() {
throw new Error('Promise already completed.');
};
// complete all waiting (async) then()s
var aThen, i = 0;
while (aThen = this._thens[i++]) {
aThen[which] && aThen[which](arg);
}
delete this._thens;
}
tiny closure Promise.js
function complete(type, result) {
promise.then = type === 'reject' ?
function(resolve, reject) {
reject(result);
} :
function(resolve) {
resolve(result);
};
promise.resolve = promise.reject = function() {
throw new Error("Promise already completed");
};
var i = 0,
cb;
while (cb = callbacks[i++]) {
cb[type] && cb[type](result);
}
callbacks = null;
}
I want to understand, why is there a loop being run through the array to handle the next function chained to the resolve !
what am i missing in my architecture?
in my library, after every resolve call, i am calling the next callback in the stack and shifting the array by one
That's a callback queue, not a promise. A promise can be resolved only once. When making a .then() chain of promises, it simply creates multiple promise objects, each one representing the asynchronous result after a step.
You might want to have a look at the core features of promises.
I'm putting together a promise chain, new to this so apologies. What I want to do is, for any errors in any particular function, I want to wait a second and try again until it succeeds, then continue with the promise chain as per the original plan.
I tried an if statement that called the function again if there was an error message and resolved if there was not, but this would not continue with the promise chain on a success.
I then worked a solution that included a for loop built into the function, as below:
var firstMethod = function() {
var promise = new Promise(function(resolve, reject){
setTimeout(function() {
console.log('first method completed');
resolve();
}, 1000);
});
return promise;
};
var secondMethod = function(someStuff) {
var promise = new Promise(function(resolve, reject){
for(let randomNumber ; randomNumber < .9||randomNumber == null ; )
{
setTimeout(function() {
randomNumber = Math.random();
console.log('the number wasn\'t big enough');
if (randomNumber>.9)
{
console.log("got a number big enough");
resolve();
}
}, 1000)
}
});
return promise;
};
var thirdMethod = function(someStuff) {
var promise = new Promise(function(resolve, reject){
setTimeout(function() {
console.log('third method completed');
resolve();
}, 1000);
});
return promise;
};
firstMethod()
.then(secondMethod)
.then(thirdMethod);
It completes firstMethod and then hangs when I try to run it. If I comment out the setTimeout in the secondMethod it works, but I feel this is not emulating the real case scenario I want to use this for.
Where am I going wrong? Is there a better way to loop mid promise chain until you achieve a particular result, then continue with the promise chain?
You can replace the secondMethod with
var secondMethod = function (someStuff) {
var promise = new Promise(function (resolve, reject) {
let randomNumber;
(function doItUntilGetEnoughBigNumber() {
setTimeout(function () {
randomNumber = Math.random();
if (randomNumber > .9) {
console.log("got a number big enough", randomNumber);
resolve();
} else { // edited: message moved her
console.log('the number wasn\'t big enough', randomNumber);
doItUntilGetEnoughBigNumber();
}
}, 1000)
})()
});
return promise;
};
The reason your code doesn't work, because the condition to stop for loop is in asynchronous, so it will be deferred, so the for loop will go forever.
Update Without using IIFE
var secondMethod = function (someStuff) {
var promise = new Promise(function (resolve, reject) {
let randomNumber;
function doItUntilGetEnoughBigNumber() {
setTimeout(function () {
randomNumber = Math.random();
if (randomNumber > .9) {
console.log("got a number big enough", randomNumber);
resolve();
} else {
console.log('the number wasn\'t big enough', randomNumber);
doItUntilGetEnoughBigNumber();
}
}, 1000)
}
doItUntilGetEnoughBigNumber();
});
return promise;
};
The important thing about async events (e.g. when setTimeout finishes) is that the handler is not called directly, but the event is put into the so called event queue. The javascript engine (the event loop) takes one event after another out of it and executes it, when its done it takes the next one. At first it will execute your for loop and set all the timeouts. Then when its done and one second passed it will start executing the setTimeout handlers. But as your for loop is infinite, it will never reach this step.
The easiest would be to use the async syntax:
const timer = ms => start => new Promise(res => setTimeout(res, ms));
async function loop(){
while(true){
await timer(1000)();
if(Math.random() > 0.9) return;
}
}
timer(1000)()
.then(loop)
.then(timer(1000))
.then(() => console.log("done"));
Alternatively you could use a recursive async function:
async function loop(){
await timer(1000)();
if(Math.random() > 0.9)
return true;
else
return loop();
}
Recently i heard 'promises' and tried to learn how it works and say...
it's not working well.
function example(){
return new Promise(function(){
var i=0;
while(i<5000){console.log(i);i++;}
})
}
example()
.then(
function(){
console.log('done')
},
function(){
console.log('error')
});
it writes 1-4999 but never writes 'done' or 'error'.. how can I make it write 'then' clause?
Your kind reply would be appreciated.
Promises gets two arguments: resolve & reject that will be the functions to be executed as the defined at the .then() & .catch(). So to be able get print the 'done' or 'error' You should do something like this:
function example(condition){
return new Promise(function(resolve, reject){
var i=0;
while(i<5000){console.log(i);i++;}
if(condition){
resolve();
} else {
reject();
}
})
}
example(true).then(function(){console.log('done')},function(){console.log('error')});//Prints 'done'
example(false).then(function(){console.log('done')},function(){console.log('error')});//Prints 'error'
function example() {
return new Promise(function(resolve,reject) {
var i = 0;
while (i < 5000) {
console.log(i);
i++;
}
resolve(i);
})
}
example()
.then(
function() {
console.log('done')
},
function() {
console.log('error')
});
You need resolve, reject callbacks. In your code your Promise always stays in pending state.
Something like... https://www.promisejs.org/patterns/
Ex.:
function example() {
return new Promise(function(resolve, reject){
try{
var i = 0;
while(i < 5000){
console.log('proccess',i);
i++;
}
resolve(i);// an if, to resolve or reject...
} catch(error) {
reject(error);
}
});
}
console.log('will call the promise');
example().then(function(success){
console.log('caller promise result', success);
}).catch(function(error){
console.log('if some problem, catch',error.toString());
});
console.log('Here, will be before or after the promise resolves');