I have this script that get random videos in the web.
The function getRandomVideo() returns a Promise with a list of urls.
What I want to do is print the data by calling main().then(data => console.log(data)). The problem is that data is being printed before the function is done running. So when I start the program I get undefined and then after the function is done I get the actual data.
I thought that what is inside .then() would run just after the promise is returned.
Does anyone know what is happening?
const main = async () => {
let allData = [];
getRandomVideo().then((videoLinks) => {
allData = videoLinks;
return new Promise((resolve) => {
resolve(allData);
});
});
};
main().then((data) => console.log(data));
As others have said, your code is full of anti-patterns in an attempt to avoid the basics of asynchronous programming with promises. All you need is this:
const main = function() => {
return getRandomVideo();
}
main().then(data => console.log(data)).catch(err => console.log(err));
Or, of course, you don't even need main() at all. You can just do:
getRandomVideo().then(data => console.log(data)).catch(err => console.log(err));
Some anti-patterns in your original code:
Not returning anything from your main() function.
Assigning an asynchronous value to a higher scoped variable and hoping that it somehow fixes asynchronous programming issues. It is nearly always a warning that you're doing something wrong when you assign an asynchronously retrieved value to a higher scoped variable because the code in the higher scope generally has no idea when that value will actually be available. Only within the .then() handler so you know when the value is present.
Manually creating a promise for no particular reason.
Marking a function async for no reason.
No error handling (e.g. .catch() handler) when calling a function that returns a promise.
If you want to manipulate the results of getRandomVideo before you send it back to the caller, then you could have a reason to use async/await
const main = async () => {
let videos = await getRandomVideo();
// define some filter function for the videos array and
// return the filtered result as the resolved value
return videos.filter(...);
}
main().then(data => console.log(data)).catch(err => console.log(err));
I think we can understand main function step by step:
let allData = []; //Defined allData variable
call getRandomVideo function
The thread wait getRandomVideo completed and return videoLinks array when you call .then function
allData = videoLinks; //Asign videoLinks array to allData variable
return new Promise((resolve) => { resolve(allData) }) //Return a Promise, that will tell the thread that you will do that later, on the other hand, after few second it's will comback and return all data by call resolve function
End of main function
==>> So as you can see, main function don't return a Promise imediately, you can't use .then to wait until it completed
Solution: Let wrap main function detail by a Promise like that
const main= () => {
return new Promise((resolve) => {
getRandomVideo().then((videoLinks) => {
resolve(videoLinks);
});
});
}
And clean your code by drop allData variable.
Related
I have this code that returns the speed score of a website through google API. As sometimes the value is not correct, i read on a post, that is a good practice to make the request few times and then make the median of the score . How i can make multiple Api request simultaneously ?
I tried something like this
function medianSpeed() {
let values = [];
function call() {
return fetch(url)
.then((response) => response.json())
.then((json) => {
const speed = +json.lighthouseResult.categories.performance.score;
values.push(speed);
});
}
const promise1 = call(),
promise2 = call(),
promise3 = call();
const promises = [promise1, promise2, promise3];
Promise.allSettled(promises).then(() => {
return values;
});
}
now the thing is if i call medianSpeed() the code doesn't wait the end of Promise.allSettled it returns straight undefined
Inside medianSpeed() add return before the call to Promise.allSettled():
return Promise.allSettled(promises).then(() => {
return values;
});
and when calling medianSpeed(), calc the median in .then() callback:
medianSpeed().then(values=>calcMedian(values))
Your issue here is that medianSpeed isn't returning a promise, so there's nothing to wait for.
If you did return Promise.allSettled... and then did a then block after calling the function: medianSpeed().then(values => {}) then you would get the values back.
I assume that the url variable is coming from somewhere else?
I'm new to promises, so apologize for the newbie question. Inside my function, I have the following lines (middle of the function):
...
const retPromise = await buildImgs(file, imgArray);
retPromise.then(async function () {
console.log("completed build imgs");
...
My assumption was that the "then" from the promise would not execute until the await was completed. but alas, it is acting like sync code, and the retPromise evaluating the "then" before the buildImgs is completed (as measured by my console.log flows). The result is an undefined retPromise.
please help...what am I missing in the concept?
OK: after feedback, let me explaing further my question:
I am trying to understand this async/sync flow and control concept:
const retVal = somefunc();
console.log(retVal);
const retVal = await somefunc();
console.log(retVal);
in the first case, if I understand sync / async code correctly, then I should have a possibility that retVal is undefined when the console.log finds it...
in the second case, I thought it would stop flow until that point that somefunc() completes, then the flow would continue. However my reading seems to indicate it will still try to run the console.log message as a parallel thread. So this leads me to believe I would need to put the console.log inside of the .then after somefunc. Which leads me to promises. So I made a promise return, which I see happening.
However, the .then, as in my original post code, seems to post the console message "completed build imgs", before code inside my buildImgs completes (measured by time I know the function to take, and also console messages inside the buildImgs to help me with sequencing)
so it seems to me I am still missing a fundamental on how to block flow for async code. :(
When you use await construction the script waits until the promise resolves and return to your retPromise value from this promise.
So in this case better to choose one. Remove await and keep then, or keep await and use retPromise value.
Assuming that buildImgs is actually returning a promise (example)
const buildImgs = (file, imgArray) => {
return new Promise((resolve, reject) => {
try {
// ...
resolve()
} catch (err) {
reject(err)
}
})
}
When you call await on a promise its already waiting for the promise to complete, if you remove the await on the call then you can use .then
there are two ways to write promise handlers, the older way looks like this
buildImgs(file, imgArray)
.then(() => {
console.log('im done')
})
.catch(err => {
console.error(err)
})
and the newer syntax
// you must declare "async" in order to use "await"
const asyncFunction = async () => {
try {
await buildImgs(file, imgArray)
console.log('im done')
} catch(err) {
console.error(err)
}
}
asyncFunction()
if you need the return value from the promise, then you would assign it to a variable
const ineedResponse = await buildImgs(file, imgArray)
console.log(ineedResponse)
// or
buildImgs(file, imgArray)
.then(ineedResponse => {
console.log(ineedResponse)
})
.catch(err => {
console.error(err)
})
I am looking at https://www.promisejs.org/patterns/ and it mentions it can be used if you need a value in the form of a promise like:
var value = 10;
var promiseForValue = Promise.resolve(value);
What would be the use of a value in promise form though since it would run synchronously anyway?
If I had:
var value = 10;
var promiseForValue = Promise.resolve(value);
promiseForValue.then(resp => {
myFunction(resp)
})
wouldn't just using value without it being a Promise achieve the same thing:
var value = 10;
myFunction(10);
Say if you write a function that sometimes fetches something from a server, but other times immediately returns, you will probably want that function to always return a promise:
function myThingy() {
if (someCondition) {
return fetch('https://foo');
} else {
return Promise.resolve(true);
}
}
It's also useful if you receive some value that may or may not be a promise. You can wrap it in other promise, and now you are sure it's a promise:
const myValue = someStrangeFunction();
// Guarantee that myValue is a promise
Promise.resolve(myValue).then( ... );
In your examples, yes, there's no point in calling Promise.resolve(value). The use case is when you do want to wrap your already existing value in a Promise, for example to maintain the same API from a function. Let's say I have a function that conditionally does something that would return a promise — the caller of that function shouldn't be the one figuring out what the function returned, the function itself should just make that uniform. For example:
const conditionallyDoAsyncWork = (something) => {
if (something == somethingElse) {
return Promise.resolve(false)
}
return fetch(`/foo/${something}`)
.then((res) => res.json())
}
Then users of this function don't need to check if what they got back was a Promise or not:
const doSomethingWithData = () => {
conditionallyDoAsyncWork(someValue)
.then((result) => result && processData(result))
}
As a side node, using async/await syntax both hides that and makes it a bit easier to read, because any value you return from an async function is automatically wrapped in a Promise:
const conditionallyDoAsyncWork = async (something) => {
if (something == somethingElse) {
return false
}
const res = await fetch(`/foo/${something}`)
return res.json()
}
const doSomethingWithData = async () => {
const result = await conditionallyDoAsyncWork(someValue)
if (result) processData(result)
}
Another use case: dead simple async queue using Promise.resolve() as starting point.
let current = Promise.resolve();
function enqueue(fn) {
current = current.then(fn);
}
enqueue(async () => { console.log("async task") });
Edit, in response to OP's question.
Explanation
Let me break it down for you step by step.
enqueue(task) add the task function as a callback to promise.then, and replace the original current promise reference with the newly returned thenPromise.
current = Promise.resolve()
thenPromise = current.then(task)
current = thenPromise
As per promise spec, if task function in turn returns yet another promise, let's call it task() -> taskPromise, well then the thenPromise will only resolve when taskPromise resolves. thenPromise is practically equivalent to taskPromise, it's just a wrapper. Let's rewrite above code into:
current = Promise.resolve()
taskPromise = current.then(task)
current = taskPromise
So if you go like:
enqueue(task_1)
enqueue(task_2)
enqueue(task_3)
it expands into
current = Promise.resolve()
task_1_promise = current.then(task_1)
task_2_promise = task_1_promise.then(task_2)
task_3_promise = task_2_promise.then(task_3)
current = task_3_promise
effectively forms a linked-list-like struct of promises that'll execute task callbacks in sequential order.
Usage
Let's study a concrete scenario. Imaging you need to handle websocket messages in sequential order.
Let's say you need to do some heavy computation upon receiving messages, so you decide to send it off to a worker thread pool. Then you write the processed result to another message queue (MQ).
But here's the requirement, that MQ is expecting the writing order of messages to match with the order they come in from the websocket stream. What do you do?
Suppose you cannot pause the websocket stream, you can only handle them locally ASAP.
Take One:
websocket.on('message', (msg) => {
sendToWorkerThreadPool(msg).then(result => {
writeToMessageQueue(result)
})
})
This may violate the requirement, cus sendToWorkerThreadPool may not return the result in the original order since it's a pool, some threads may return faster if the workload is light.
Take Two:
websocket.on('message', (msg) => {
const task = () => sendToWorkerThreadPool(msg).then(result => {
writeToMessageQueue(result)
})
enqueue(task)
})
This time we enqueue (defer) the whole process, thus we can ensure the task execution order stays sequential. But there's a drawback, we lost the benefit of using a thread pool, cus each sendToWorkerThreadPool will only fire after last one complete. This model is equivalent to using a single worker thread.
Take Three:
websocket.on('message', (msg) => {
const promise = sendToWorkerThreadPool(msg)
const task = () => promise.then(result => {
writeToMessageQueue(result)
})
enqueue(task)
})
Improvement over take two is, we call sendToWorkerThreadPool ASAP, without deferring, but we still enqueue/defer the writeToMessageQueue part. This way we can make full use of thread pool for computation, but still ensure the sequential writing order to MQ.
I rest my case.
First of all, there are some issues with console.log in Google Chrome not functioning as expected. This is not the case as I am working in VSCode.
We begin with two async calls to the server.
promise_a = fetch(url)
promise_b = fetch(url)
Since fetch results are also promises, .json() will needed to be called on each item. The helper function process will be used, as suggested by a Stackoverflow user -- sorry lost the link.
let promiseResults = []
let process = prom => {
prom.then(data => {
promiseResults.push(data);
});
};
Promise.all is called. The resulting array is passed to .then where forEach calls process on item.json() each iteration and fulfilled promises are pushed to promiseResults.
Promise.all([promise_a, promise_b])
.then(responseArr => {
responseArr.forEach(item => {
process(item.json());
});
})
No argument is given to the final .then block because promiseResults are in the outer scope. console.log show confusing results.
.then(() => {
console.log(promiseResults); // correct results
console.log(promiseResults[0]); // undefined ?!?
})
Any help will be greatly appreciated.
If you are familiar with async/await syntax, I would suggest you not to use an external variable promiseResults, but return the results on the fly with this function:
async function getJsonResults(promisesArr) {
// Get fetch promises response
const results = await Promise.all(promisesArr);
// Get JSON from each response promise
const jsonResults = await Promise.all(results.map(r => r.json()));
return jsonResults
}
This is usage example:
promise_a = fetch(url1)
promise_b = fetch(url2)
getJsonResults([promise_a, promise_b])
.then(theResults => console.log('All results:', theResults))
Use theResults variable to extract necessary results.
You can try this, it looks the array loop is not going properly in the promise env.
Specifically: the promiseResults is filled after you are logging.
var resultAll = Promise.all([promise_a, promise_b])
.then(responseArr => {
return Promise.all(responseArr.map(item => return item.json()));
});
resultAll.then(promiseResults => {
console.log(promiseResults);
});
I am trying to run async processes in order using chained promises.
I started with this:
var dataStore = {};
DBCalls.GetAllProjects()
.then((data) => ProcessData.StoreProjects(data,dataStore))
.then(ProcessData.DoStuff(dataStore))
The above finished the DoStuff function before StoreProjects function. (Running in wrong order)
var dataStore = {};
DBCalls.GetAllProjects()
.then((data) => ProcessData.StoreProjects(data,dataStore))
.then(() => {ProcessData.DoStuff(dataStore)})
This ran the function in correct order.
Can anyone explain what the differences in the syntax are?
Is it because the StoreProjects Resolve is returning nothing and that the callback signatures are different?
Extra Info:
All functions used return promises.
If your functions return promises, you should be using the return statement in the promise handlers if you want to wait for the promise to resolve before moving on to the next handler in the chain.
var dataStore = {};
return DBCalls.GetAllProjects()
.then((data) => { return ProcessData.StoreProjects(data, dataStore); })
.then(() => { return ProcessData.DoStuff(dataStore); })