I was following along the MDN article on async/await and understanding it fairly well and then
I had a brain fart, and I'm not sure what's going on now. This example is an MDN example from the article on async/await.
async function makeResult(items) {
let newArr;
for (let i = 0; i < items.length; i++) {
newArr[i].push('word_' + i);
}
return newArr;
}
async function getResult() {
let result = await makeResult(["1", "2"]);
console.log(result);
}
How do I get this code to log the result to the console? I get errors when trying to call these functions from the main flow of the program. What am I supposed to do? I'm familiar with Promises, .then() chaining, and how to use async await in one basic async function, but not sure what to do here.
Edit: The problem was that newArr wasn't initialized as an empty array, and that I was pushing to newArr[i] instead of newArr. This is all very strange since the code snippet on https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Async_await was written incorrectly.
If a function is asynchronous but doesn't do any async work, is it really asynchronous? If a tree falls in the woods...
The first function would return aPromise<Array<string>> if the code in it worked, the second function would return a Promise<undefined> because there's no return. You can see this by commenting out the first line in the second function body, then console.log(getResult()). The first function has logic problems (newArr is undefined, but it looks like the loop wants it to be a 2d array).
To log the result of an async function to the console, you can do what you already know (using .then), or await it in another async function:
const foo = async () => true
// this works
foo().then(console.log)
// this also works
;(async () => {
const bool = await foo()
console.log(bool)
})()
You have some problems in your code.
1 - Your newArr is undefined since you haven't initialized it.
2 - You are trying to push into an array that is in newArr[i] instead of pushing into newArr. Since there is nothing in newArr, the push returns undefined.
Async works just the way you have written here. You can combine the promises to wait for each other. And, the return statement will convert into a promise.
For example, the following:
async function foo() {
return 1
}
...is equivalent to:
function foo() {
return Promise.resolve(1)
}
Looking at your code,
async function makeResult(items) {
let newArr= [];
for (let i = 0; i < items.length; i++) {
newArr.push('word_' + i);
}
return newArr;
}
async function getResult() {
let result = await makeResult(["1", "2"]);
console.log(result);
}
getResult();
The problem with the code you've shown is that you're wrongfully using the array.
You need to initialize with an empty array:
let newArr = [];
In your loop, you're trying to use push on non-existent array-elements. Instead you want to use it on the array itself:
newArr[i].push('word_' + i);
async function makeResult(items) {
let newArr = [];
for (let i = 0; i < items.length; i++) {
newArr.push('word_' + i);
}
return newArr;
}
async function getResult() {
let result = await makeResult(["1", "2"]);
console.log(result);
}
getResult();
As it turns out, MDN has this exact faulty code example. I've submitted an issue for it: https://github.com/mdn/content/issues/1202
just do it like below, because you did wrong in makeResult(items) function array declaration/use block:
function makeResult(items) {
let newArr = [];
for (let i = 0; i < items.length; i++) {
newArr.push('word_' + i);
}
return newArr;
}
async function getResult() {
let result = await makeResult(["1", "2"]);
console.log(result);
}
//testing
getResult()
Related
Say I have the following generator that produces an infinite range:
const generateInfiniteRange = function* () {
for (let i = 0; true; i++) {
yield i;
}
};
const infiniteRange = generateInfiniteRange();
const infiniteRange$ = from(infiniteRange);
I can do something like this with RxJS:
const predicate = i => i < 10;
infiniteRange$.pipe(
takeWhile(predicate),
);
Now let's say that the predicate is asynchronous:
const predicate = async i => i < 10;
infiniteRange$.pipe(takeWhile(predicate)).subscribe(console.log);
How can I make this code work?
infiniteRange$.pipe(takeWhile(predicate));
I've tried using map as follows:
infiniteRange$.pipe(
map(async i => ({
i,
predicateResult: await predicate(i),
})),
takeWhile(({predicateResult}) => predicateResult),
pluck('i'),
);
but that just ends up mapping everything to a promise that always coerces to a truthy value, so everything passes through the takeWhile
I figured based on a previous question I've asked (for which originally I asked this question as an addendum in a comment of an answer, before deciding a question was more appropriate) that I could use concatMap, but that just generates an infinite emission before any of the internal observables hit the pipe.
If I understand this right, you can not achieve what you want.
The simple reason is that when, with from you create an Observable out of a generator, then you are creating a synchronous Observable, i.e. an Observable which emits all its values synchronously.
You can see clearly this looking at the fromIterable source code
function fromIterable<T>(iterable: Iterable<T>) {
return new Observable((subscriber: Subscriber<T>) => {
for (const value of iterable) {
subscriber.next(value);
if (subscriber.closed) {
return;
}
}
subscriber.complete();
});
}
As you can see, the for loop is exited only when the subscriber is closed. But in our case the subscriber will be closed asynchronously, i.e. we need Node to stop the execution of the loop since no instructions are left and pick the next callback, the one that runs the predicate. This will never happen since the for loop will never end.
So the summary is that you can not have an async predicate working with a synchronous infinite stream of values, which is what you create using a Generator.
By the way, in order for the code to compile, you need to use concatMap to transform the value notified by the source stream to the Object used by the predicate. So a code that compiles is this one
infiniteRange$.pipe(
tap(i => {
console.log(i)
}),
concatMap(async i => {
console.log('concatMap hit');
return {
i,
predicateResult: await predicate(i),
}
}),
takeWhile(({predicateResult}) => {
console.log('takeWhile hit');
return predicateResult
}),
pluck('i'),
).subscribe(console.log);
Running this snippet, you will see that you enter once in the concatMap input function (i.e. "concatMap hit" will be printed once) while you never enter the function passed to takeWhile (i.e. "takeWhile hit" will never be printed).
A SOLUTION WITH ASYNC GENERATORS
Actually, if you change the generator to be async, then using concatMap in the pipe we can reach the result you are looking for.
This is how the code would look like
const generateInfiniteRangeAsync = async function* () {
for (let i = 0; true; i++) {
await new Promise(resolve => setTimeout(resolve, 1000));
yield i;
}
};
const predicate = async i => i < 10;
const infiniteRangeAsync = generateInfiniteRangeAsync();
const infiniteRangeAsync$ = from(infiniteRangeAsync);
infiniteRangeAsync$.pipe(
concatMap(async i => {
console.log('concatMap hit');
return {
i,
predicateResult: await predicate(i),
}
}),
takeWhile(({predicateResult}) => {
console.log('takeWhile hit');
return predicateResult
}),
pluck('i'),
)
.subscribe(console.log);
A simple solution, using iter-ops, which can handle asynchronous predicates out of the box:
import {pipeAsync, stop} from 'iter-ops';
// your infinite generator:
const generateInfiniteRange = function * () {
for (let i = 0; true; i++) {
yield i;
}
};
// create our iterable:
const i = pipeAsync(generateInfiniteRange(), stop(async a => a >= 10));
// test:
(async function () {
for await(const a of i) {
console.log(a); //=> 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
}
})();
P.S. I am the author of the library.
While I was prototyping some logic I need to implement for my project, I noticed some interesting behaviour with async-await and Promise.
// Notice this one returns a Promise
var callMe = function(i) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(i)
resolve(`${i} is called :)`)
}, (i+1)*1000)
})
}
// But this one doesn't
var callYou = function(i) {
setTimeout(() => {
console.log(i)
}, (i+1)*1000)
}
async function run() {
console.log("Start")
for(let i = 0; i < 3; i++) {
let val = await callYou(i)
# also try with callMe()
#let val = await callMe(i)
console.log(val)
}
console.log("End")
}
run()
With let val = await callYou(i), the result looks something like this
Start
callYou()
callYou()
callYou()
End
0
1
2
whereas with let val = await callMe(i), the result looks something like this
Start
0
0 is called :)
1
1 is called :)
2
2 is called :)
End
I was expecting two functions to behave similarly since async function essentially returns a promise. Could someone shed light on why this is happening please?
async functions return promises but neither callYou or callMe are async functions and even if they where, callYou will be fulfilled when the function returns not when the callback passes to the setTimeout function is executed.
As part of an exericse, I'm re-writing underscore functions and testing them in jsfiddle. Every time I pass a callback function, I get "undefined".
My code is below:
each = function(collection, iterator) {
if(Array.isArray(collection)){
for (var i = 0; i < collection.length; i++) {
iterator(collection[i], i, collection);
}
} else {
for(var key in collection) {
iterator(collection[key], key, collection);
}
}
};
var numbers = [1,2,3,4];
var result = each(numbers, function(num) {
return num * 2;
});
console.log(result);
// undefined
Any idea what I'm doing wrong and why it's not outputting on jsfiddle?
You aren't doing anything wrong. Your each function isn't returning anything. This is fine, since each functions don't necessarily have to return anything. You might be thinking along the lines of a map or reduce function which compile the results of calling the callback on each item in the collection, and then return that compilation.
The callback you pass to an each function doesn't normally return anything. Think of an each like it's just syntactic sugar for a normal for loop; for loops don't return anything (obviously..), they just perform generic operations with the items in the collection and the variables that have been declared in the containing scope.
That being said, if you want to emulate underscore (or any good library), you will want to return the collection to enable chaining. From the underscore docs:
[each] Iterates over a list of elements, yielding each in turn to an iteratee function....Returns the list for chaining.
This is just good practice so as to avoid annoying the developers who may use your library and are used to being able to chain everything in JavaScript.
So all you'd need to do is put
return collection;
at the end of your each function and you're good. Cheers.
You are not aggregating the result of your operation that's why the result is not being result.
Here is a quick stab at how this can be fixed for arrays.
each = function(collection, iterator) {
var arr = [];
if(Array.isArray(collection)){
for (var i = 0; i < collection.length; i++) {
arr.push( iterator(collection[i], i, collection) );
}
return arr;
} else {
for(var key in collection) {
iterator(collection[key], key, collection);
}
}
};
var numbers = [1,2,3,4];
var result = each(numbers, function(num) {
return num * 2;
});
console.log(result);
jsfiddle
function(obj){
for (property in obj) {
if (obj.hasOwnProperty(property)) {
// some code here
if(condition){
obj.children = example.getdata(base, obj.name);
}
// some more code releated to obj
}
}
if (obj.length == 10)
{
//some more function
}
}
Now i want to make example.getdata async but i dont want to execute if statement that is after for loop until all the sync tasks are done.
Its more of like i want all the example.getdata function calls to execute in parallel and after they finish work i execute if (obj.length).
I tried using promises and push all the promises and resolve them but i dont know how to handle the return value for each function call.
You can use Promise s.
Think of something like:
var jobs = get_jobs(data);
when_all(jobs).done(function (jobs_result) {
console.log(jobs_result);
});
Where get_jobs returns a list or Promises and when_all will resolve when all it's input jobs are resolved.
To run async tasks serially in a loop, you can't use a regular for loop because the for loop won't wait for your async operation to complete before executing the next cycle of the loop. Rather, you have to do your own custom iteration a slightly different way. Here's one way:
Assuming your getdata() function is actually asynchronous and has a completion function, you could structure things like this:
function myFunc(obj, callback) {
var keys = Object.keys(obj);
var cntr = 0;
var results = [];
function next() {
if (cntr < keys.length) {
example.getdata(obj[keys[cntr++]], function(result) {
// do something with the result of each async operation here
// and put it in the results array
// kick off the next iteration
next();
});
} else {
// call the final callback because we are done now with all async operations
callback(results);
}
}
// start the first iteration
next();
}
If your getData function returns a promise or can be made to return a promise, then you can use promises for this too:
function myFunc(obj) {
var keys = Object.keys(obj);
keys.reduce(function(p, item) {
return p.then(function(result) {
// do something with the result of each async operation here
// and put it in the results array
return example.getdata(obj[item]);
});
}, Promise.resolve());
}
myFunc.then(function(results) {
// all async operations done here, use the results argument
});
Here is a clean example of a for loop using promises. If you can pick a promise library libraries like Bluebird will make it even simpler:
function(obj){
var props = Object.keys(obj), p = Promise.resolve();
props.forEach(function(prop){
p = p.then(function(){
if(condition){ // can access obj[prop] and obj here
obj.children = example.getData(...); // getData returns a promise
return obj.children; // return the promise.
}
});
});
Promise.resolve(obj.children).then(function(children){
// here the async call to get `.children` is done.
// can wait for `p` too (the loop) if we care about it
if(obj.length === 10) // do stuff
});
Is there a way that i could call a Async Method in a loop, put all the results in a array & return the results in the end.
Pseudo Code of what i want to do:
methodThatRunsAsync(callback){
once completes, invoke callback;
}
anotherMethod (){
var result = [];
for(i=1; i=10; i++){
methodThatRunsAsync(function(resp){
result.push(resp);
});
return result; }
}
But the value of result is always the default value. How can i trap the results of the async block in a sync block and return the same to the caller.
Looking into Promise framework, but finding it a bit tough to get my head around it. If anyone can please help me understand how to achieve this, psuedo code too would be great.
No, you can't return the result, as the calls as asynchronous. Use a callback for that function too, and call it when the last result is added:
function anotherMethod (callback) {
var result = [];
var count = 10;
for(i = 0; i < count; i++) {
methodThatRunsAsync(function(resp){
result.push(resp);
if (result.length == count) {
callback(result);
}
});
}
}
Note that I changed the loop. The loop that you had would not do any iterations at all.