Here is my problem:
My main function:
const mainFunction = () => {
const reports = JSON.parse($sessionStorage.datas);
// reports contains array of objects.
// consider i have two objects inside one array, so the loop will execute two times now
reports.forEach((item) => {
this.openReport(item);
});
};
mainFunction();
openReport function:
this.openReport = (report) => {
console.log('openReport working');
// some async functions will be here
// ajax calls
this.openTab(report);
};
openTab function:
this.openTab = (report) => {
console.log('handleOpenTab function working');
}
Output:
// Now i have two objects, so forEach works two times.
'openReport working'
'openReport working'
'handleOpenTab function working'
'handleOpenTab function working'
My expected output :
'openReport working'
'handleOpenTab function working'
'openReport working'
'handleOpenTab function working'
How to achieve this? i am unable to use async await inside my forEach function, because using old node version.
If its possible to use async/await for this problem, i will try to upgrade my node version.
Using promises, it would look something like this probably:
this.openReport = (report) => {
console.log('openReport working');
// some async functions will be here
// ajax calls
return new Promise(function(resolve, reject) {
ajaxCall('url', resolve); // reject as necessary!
}).then(answer => {
this.openTab(report);
return answer;
});
};
const mainFunction = () => {
const reports = JSON.parse($sessionStorage.datas);
// reports contains array of objects.
function getNextReport(index) {
if (index >= reports.length) return Promise.resolve();
let item = reports[index];
return this.openReport(item).then(function(answer) {
// do something with answer
return getNextReport(index+1);
});
}
return getNextReport(0);
};
Related
This is a simple version of what I'm trying to do in my application. I have an if statement which evaluates the result of a function call and then populates an array if the statement comes back as true. AFTER the if statement is completely finished, I want to run some more code such as the console.log as seen below.
I understand that the if's evaluation is taking too long to finish and javascript just continues to the console.log because of its asynchronicity. How do I make the code wait for the if statement to complete?
var tabs = [];
if (isTrue()) {
tabs.push('some string');
}
console.log(tabs[1]);
function isTrue() {
setTimeout(function() {
return true;
}, 500)
}
You can just wrap your code in a Promise and consume the returned values by calling then on it:
var tabs = [];
isTrue().then(res => {
if (res) {
tabs.push('some string');
}
return tabs;
}).then(arr => {
console.log(arr);
});
function isTrue() {
//Just wrap your existing code in a Promise constructor
return new Promise((resolve, reject) => {
setTimeout(() => {
//Pass whatever value you want to consume later to resolve
resolve(true);
}, 500)
});
}
You could pass a callback to the isTrue() function, something like:
function isTrue(_callback) {
setTimeout(function() {
// code here
// Call the callback when done
if (typeof(_callback) === 'function')
_callback(tabs);
});
}
function showTabs(tabs) {
console.log(tabs[1]);
}
isTrue(showTabs);
Ought to work.
Using modern javascript, you can achieve that using promises and async/await:
const isTrue = () => new Promise(resolve => setTimeout(resolve, 500, true));
// you can only use `await` inside an `async` function
async function main() {
// better use `let` instead of `var` since `let` is block scoped,
// see:
// <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let>
let tabs = [];
if (await isTrue()) {
tabs.push('some string');
}
// array's index start by 0, not 1
console.log(tabs[0]);
}
main();
(this code also use arrow functions for isTrue.)
isTrue() returns undefined. The return true inside of the setTimeout callback will return back to the timeout call, not to the isTrue() call. The code executes immeadiately and there is no asynchronity involved (except for that timer that does nothing).
Let's say I have this code:
const myFunction = async => {
const result = await foobar()
}
const foobar = async () => {
const result = {}
result.foo = await foo()
result.bar = await bar()
return result
}
And I want this:
const myFunction = () => {
const result = foobar()
}
I tried to wrap foobar like this:
const foobar = async () => {
return (async () => {
const result = {}
result.foo = await foo()
result.bar = await bar()
return result
})()
}
But this still return a promise
I can't use .then in myFunction, I need that foobar returns the result variable instead a promise.
The problem is that myFunction is an async function and it will return a promise but It should return undefine I need to get rid of async in myFunction.
Edit: as Sebastian Speitel said, I want to convert myFunction to sync
Edit 2: to Shilly, I am using nightwatch for end2end test, nightwatch will call myFunction() if there are no errors in the execution of the function it will run perfectly, if there's an error then nightwatch's virtual machines will run forever instead stop, this problem happens if the called function is async.
To change an asynchronous function into a normal synchronous function you simply have to drop the async keyword and as a result all await keywords within that function.
const myFunction = async () => {
const result = await foobar();
// ...
return 'value';
};
// becomes
const myFunction = () => {
const result = foobar();
// ...
return 'value';
};
You should however keep one simple rule in mind.
You can't change a asynchronous function into a synchronous function if the return value depends on the value(s) of the resolved promise(s).
This means that functions that handle promises inside their body, but from whom the return value doesn't depend on those resolved promises are perfectly fine as synchronous functions. In most other scenarios you can't drop the asynchronous behaviour.
The following code gives you an example for your situation, assuming the return value of myFunction doesn't depend on the resolved promise.
const myFunction = () => {
const result = foobar();
result.then(data => doSomethingElse(data))
.catch(error => console.error(error));
return 'some value not dependent on the promise result';
};
If you want to learn more about promises I suggest checking out the promises guide and the async/await page.
Have you looked into using .executeAsync() and then having the promise call the .done() callback? That way it should be possible to wrap foobar and just keep either the async or any .then() calls inside that wrapper.
My nightwatch knowledge is very stale, but maybe something like:
() => {
client.executeAsync(( data, done ) => {
const result = await foobar();
done( result );
});
};
or:
() => {
client.executeAsync(( data, done ) => foobar().then( result => done( result )));
};
Any function marked with async will return a Promise. This:
const foobar = async () => {
return 7;
}
Will a return a Promise of 7. This is completely independent of wether the function that calls foobar is async or not, or uses await or not when calling it.
So, you're problem is not (only) with myFunction: is foobar using async which forces it to always return a Promise.
Now, said that, you probably can't achieve what you want. Async-Await is only syntax sugar for promises. What you're trying is to return a synchronous value from an asynchronous operation, and this is basically forbidden in javascript.
You're missing very important understanding here between the synchronous and asynchronous nature of the code.
Not every async function can be converted to synchronous function. You can use callback pattern instead of await/async, but I doubt that would be useful to you.
Instead I would recommend you just use await, as in your first code example, and leave functions as async, it shouldn't harm your logic.
Check this out
function foo(){
return 'foo'
}
function bar(){
return 'bar'
}
const foobar = () => {
return new Promise((resolve)=>{
let result = {}
result.foo = foo()
result.bar = bar()
return resolve(result)
})
}
const myFunction = () => {
const result = foobar()
let response = {}
result.then(val=>{
response = Object.assign({}, val);
return response
});
}
var test = myFunction()
console.log(test)
What's the proper way to chain something when you need to append the results to an array that is sitting at the top level of a function's scope?
function run() {
let array = []
let input = 'object'
promiseA(input)
.then((result) => {
array.push(result)
})
promiseB(input)
.then((result) => {
array.push(result)
})
console.log(array.join(' '))
}
Order doesn't matter for my application, I can parallelize it if that's considered best practices. It's literally just checking for a condition, there's no async calls to get results from an API or anything like that.
You should use Promise.all to wait for promise A and promise B to complete. Promise.all will receive an array of results (from each Promise) that you can then use.
You might have something like:
var promiseA = doSomethingThatReturnsPromise(input);
var promiseB = doSomethingThatReturnsPromise(anotherInput);
Promise.all([promiseA, promiseB]).then(function(resultsArray) { // do something });
your function will look like this
function run () {
return Promise.all([promiseA(), promiseB()]).then(([resultA, resultB])=>{ })
}
An alternative is using async function:
This approach executes the promises one by one, that way you will be able to handle the result with the desired execution order.
function promiseA(input) {
return new Promise(function(resolve) {
setTimeout(function() {
resolve(input);
}, 1000);
});
}
function promiseB(input) {
return new Promise(function(resolve) {
setTimeout(function() {
resolve(input);
}, 500);
});
}
async function run() {
let array = [];
let input = 'Ele';
array.push(await promiseA(input));
input = "from SO";
array.push(await promiseB(input));
console.log(array.join(' '))
}
console.log('Wait for 1.5sec...')
run()
Answering the Question Asked
This is the correct way to sequence or chain Promises:
one(arr).then(two).then(log, fail);
This directly answers the question without offering other possible solutions.
Please note there are no side effects. The "scope" issue mentioned in the comments is absolutely avoided.
The sample snippet implements this:
let arr = [];
function one(arr) {
return new Promise((res, rej) => {
arr.push('one');
res(arr);
});
}
function two(arr) {
return new Promise((res, rej) => {
arr.push('two');
res(arr);
});
}
function log(arr){
console.log(arr);
}
function fail(reason){
console.log(reason);
}
one(arr).then(two).then(log, fail);
I need to somehow loop over the work array passed to _start then
for each of the items in the array, I need to somehow call the corresponding function with the same name.
I don't have control over the number of items in work the array or the number of items, I do know that there will always be a corresponding function.
I don't want to call all the functions at the same time, once the first function resolves after 3 seconds, I then want to call the second function, once the second function resolves after 3 seconds I then want to call the third function. Once the third function resolves after another 3 seconds I want to call _done().
In this example each function takes 3 seconds to complete _done wont gete called for 9 seconds.
function _start(data){
// Insert some kinda native magic loop
}
function _one(){
return new Promise((resolve, reject) => {
setTimeout(function(){
resolve(1);
}, 3000);
})
};
function _two(){
return new Promise((resolve, reject) => {
setTimeout(function(){
resolve(2);
}, 3000);
})
};
function _done(){
console.log('All done in 9 seconds)
}
(function(){
var work = ['_one', '_two', '_two'];
_start(work);
})();
Given the order is dicated by the array, you can use reduce to aggregate the promises into a chain
const _start = (...actions) => {
return actions.reduce((chain, action) => {
const func = this[action];
return chain.then(() => func());
}, Promise.resolve());
}
...
_start('_one', '_two', '_three').then(() => console.log('All done'));
See it in action - the example appends an extra then to the chain just to output any results from the promises (probably outwith the scope of this question but something you may have to consider if getting data back is required).
Update
Can see you intend on invoking _start from a different context in which the functions are declared, this is fine but you need to make sure you set the correct context before hand i.e.
const self = this;
(function() {
_start.bind(self)('_one', '_two', '_two');
})();
A function which creates a promise which sleeps:
const sleep = n => () => new Promise(resolve => setTimeout(resolve, n));
A function which sleeps after some input promise:
const sleepAfter = n => p => p.then(sleep(n));
A function which chains a bunch of promises, represented by functions:
const chain = (...promises) => promises.reduce((ret, promise) => ret.then(promise),
Promise.resolve());
Run a bunch of functions yielding promises, sleeping in between:
const _start = promises => chain(promises.map(sleepAfter(3000)));
Now just:
_start(_one, _two, _three).then(_done);
Try using this:
_one().then((firstResponse) {
return_two();
}) .then((secondResponse) => {
*second and first respone are already done*
});
Use promises then
_one().then((responseOne) => {
return _two();
}).then((responseTwo) => {
// _one & _two are done
});
In a middle of already completed javascript function, you want to call a new async function. So you need to pass, "rest of the code" as a callback function as parameter to this new function.
function sample() {
alert("a bunch of codes");
alert("another a bunch of codes");
}
I have to change the function as below.
function sample() {
alert("a bunch of codes");
var cb = function () {
alert("another a bunch of codes");
};
newFunction(cb);
}
What if I want to add another function that has to wait first one ? Then I got numerous multiple levels of callback functions to the wait another..
So what is the best practice on ES5 ?
In ES5, just like you said you have to nest multiple callbacks inside each other.
Example:
function myFunction2(){
console.log(2);
let myFunction = () => {
console.log(1);
}
myFunction();
}
myFunction2();
// OUTPUT
// 2
// 1
ES6 also provides a new alternative, promises.
Example:
let myPromise = new Promise((resolve, reject) => {
setTimeout(function(){
resolve(1);
}, 250);
});
console.log(2);
myPromise.then((successMessage) => {
console.log(successMessage);
});
// OUTPUT
// 2
// 1
ES8 has provides an even better alternative(although it is just syntactic sugar based on promises) but you can use async functions with await.
Example:
function resolveAfter2Seconds(x) {
return new Promise(resolve => {
setTimeout(() => {
resolve(x);
}, 2000);
});
}
async function add1(x) {
const a = await resolveAfter2Seconds(20);
const b = await resolveAfter2Seconds(30);
return x + a + b;
}
add1(10).then(v => {
console.log(v); // prints 60 after 4 seconds.
});
Keep in mind though, that you probably need to use Babel to transpile your js in order to be compatible with all browsers.