I am just beginning to work with promises in React and cannot explain why I am returning a promise in a function and not the array I want.
The code is as follows:
async function pullTweets () {
let twitterRest = new TwitterRest(); //create a new instance of TwitterRest Class
var twatts = await twitterRest.pullTimeLine('google'); //pull the Google TimeLine
console.log(twatts);
return twatts;
}
let twitts = pullTweets();
console.log(twitts);
The console.log(twatts); is returning the correct array of tweets; however, the console.log(twitts) is returning a promise.
Any explanation would be greatly appreciated.
Thanks
You need to wait for pullTweets() which is an asynchronous function (that returns a Promise as well) to finish executing.
This can be done by using the keyword await before pullTweets():
let twitts = await pullTweets();
console.log(twitts);
The code you wrote is equivalent to this (using only Promises):
function pullTweets () {
let twitterRest = new TwitterRest();
return twitterRest.pullTimeLine('google').then((twatt) => {
// This logs the array since the promise has resolved successfully
console.log(twatt)
return twatt
})
}
let twitts = pullTweets();
// This logs a pending promise since the promise has not finished resolving
console.log(twitts);
Related
I currently have a program where I want to call several REST API:s in parallel, but I'm only interested in the result from one of them.
Currently I've solved it like this:
private async loadData () {
const all = [this.loadFirstData(), this.loadSecondData(), this.loadThirdData()];
const combine = Promise.all(all);
await combine;
// One of the promises just puts it's return value in this global variable, so that I can access it after it is done.
if (this.valueFromThirdAPI) {
// Do something with value
}
}
So what I do is just put the put the result from the promise I want a result from in a global variable that I can access after all of the promises have returned. This works, but I'm sure there must be a much better way of doing this.
Promises.all returns a array of the resolved values, but how do I distinguish them from each other if I'm only interested in the value from one of them? The other two doesn't need to return anything.
Thanks in advance!
First, it makes sense to await Promise.all,
You can access the result using the returned array's index.
Another option is using Array destructoring.
Below is an example..
const promise1 = Promise.resolve(1);
const promise2 = Promise.resolve(2);
const promise3 = Promise.resolve('this one');
const promise4 = Promise.resolve(4);
async function test() {
//note the double `,` to ignore the first 2 promises.
const [,,three] = await Promise.all([promise1, promise2, promise3, promise4]);
console.log(three);
}
test();
You can iterate on the return values when they are all resolved.
Promise.all().then(values)
Take a look at https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
Wait on the third and then await on all. So if the third finishes before first and second, you get to process it earlier.
private async loadData () {
const all = [this.loadFirstData(), this.loadSecondData(), this.loadThirdData()];
const third = await all[2];
if (third) {
// Do something with value
}
await Promise.all(all);
}
Promise.all returns an array with the same order of the given promises:
private async loadData () {
const all = [this.loadFirstData(), this.loadSecondData(), this.loadThirdData()];
const combine = Promise.all(all);
const values = await combine;
// One of the promises just puts it's return value in this global variable, so that I can access it after it is done.
if (values[2]) {
// Do something with value
}
}
Hello Stack Overflow community,
I come to you with a problem related to JS async/await. I am trying to call an async function and then log the array to where the async function pushes the results to the console. If I call it like so directly in the console:
console.log(Page.data) - I can see that it has results in it, but if it is called on click of a button it logs an empty array.
// It is a nested object so do not worry if you don't exactly understand where Page.data comes from
Page.data = []
async function f1() {
// Fetch JSON data
// Process data
// Pushes at some point to the Page.data array
}
async function f2() {
// Fetch JSON data
// Process data
// Pushes at some point to the Page.data array
}
async function f3() {
// Fetch JSON data
// Process data
// Pushes at some point to the Page.data array
}
async function load(loader) {
let fn = async function() {};
if(condition1) fn = f1;
else if(condition2) fn = f2;
else fn = f3;
// This is the line that makes me problems
// According to documentation async functions return a promise
// So why would the array in the case be empty?
// Since I am telling it to display after the function is done
await fn(loader).then(console.log(Page.data))
}
This is just a template of my code and logic. I hope that you can understand where I am going.
Your help will be much appreciated.
The await expression causes async function execution to pause until a Promise is settled (that is, fulfilled or rejected), and to resume execution of the async function after fulfillment. When resumed, the value of the await expression is that of the fulfilled Promise.
For instance (this is an MDN example with some added comments):
function resolveAfter2Seconds(x) {
return new Promise(resolve => {
setTimeout(() => {
resolve(x);
}, 2000);
});
}
async function f1() {
// note that here, you are "awaiting" the RESOLVED RESULT of the promise.
// there is no need to "then" it.
var x = await resolveAfter2Seconds(10);
// the promise has now already returned a rejection or the resolved value.
console.log(x); // 10
}
f1();
So you would "await" your function, which would hold up the execution until the promise either resolves or rejects. After that line, you would run your console.log, and it would log as expected. Short answer, "remove the then".
I should add, if the result of the "awaited" function is not a promise, it is converted to a promise (so technically, there is no need to return a promise, it'll wrap up your returned value for you).
the problem is that you can use then with await for the same method, lets check some examples provided by MDN:
this is a working Promise using async/await:
let myFirstPromise = new Promise((resolve, reject) => {
setTimeout(function() {
resolve("Success!");
}, 250)
})
const functionCaller = async() => {
const result = await myFirstPromise
console.log("the result: ", result);
}
functionCaller();
what you are trying is:
let myFirstPromise = new Promise((resolve, reject) => {
setTimeout(function() {
resolve("Success!");
}, 250)
})
const functionCaller = async() => {
// you are not returning anything here... actually you are not doing anything with the response. despite it shows something, it is not sending any response
await myFirstPromise.then(console.log("something happened"))
}
// then this function doesn't really get a value.
functionCaller();
so what you need to do in your load call, is to change it like this:
async function load(loader) {
let fn = async function() {};
if(condition1) fn = f1;
else if(condition2) fn = f2;
else fn = f3;
return await fn(loader)
}
I cannot for the life of me figure out why async/await behaves the way it does.
Consider this example.
I want a function that does some db initialization, and returns me the database object when it is done.
var globalDb = null;
const IDB_DATABASE_NAME = "mydb";
const IDB_STORE_NAME = "mystore";
const IDB_VERSION = 1.0;
async function initDb() {
var idbOpenDbRequest = window.indexedDB.open(IDB_DATABASE_NAME, IDB_VERSION);
idbOpenDbRequest.onsuccess = function (event) {
return event.target.result;
};
}
(async () => {
globalDb = await initDb();
console.log("I should happen second")
console.log(globalDb);
})();
Expected
console.log("I should happen first")
console.log("I should happen second")
console.log(globalDb); //dumps the object to console
Actual
console.log("I should happen second")
console.log(globalDb); //undefined
console.log("I should happen first")
I realize I am fundamentally misunderstanding something here. Please enlighten me why await does not work as I would expect. :)
JsFiddle
https://jsfiddle.net/p2jqyrou/2/
Ps. Forget that this is about indexedDb and that this example is extremely simplified - I don't think it matters for the topic of this question.
So the problem is with your initDb function. I'll rewrite it for you and then explain why this version does work:
function initDb() {
var idbOpenDbRequest = window.indexedDB.open(IDB_DATABASE_NAME, IDB_VERSION);
return new Promise((resolve, reject) => {
idbOpenDbRequest.onsuccess = function (event) {
setTimeout(function () {
console.log("I should happen first");
resolve(event.target.result);
}, 2000);
};
})
}
What I've done is wrap the onsuccess callback in a Promise. The async/await pattern in Javascript is fundamentally based around Promises. Either you return a Promise yourself or it wraps the result in a Promise (and immediately resolves it).
Because you did not return a Promise yourself it tried to wrap the return value (which was undefined) in a Promise. It then immediately resolved causing the async function to return.
With this updated code initDb is called which then returns a Promise which is only resolved after the connection has been made and after the timeout triggered.
Node.JS 10.15, serverless, lambdas, invoked locally
SAMPLE A) This Works:
export async function main(event) {
const marketName = marketIdToNameMap[event.marketId];
const marketObject = marketDirectory[marketName];
const marketClient = await marketObject.fetchClient();
const marketTime = await marketObject.getTime(marketClient);
console.log(marketTime);
}
SAMPLE B) and this works:
export function main(event) {
const marketName = marketIdToNameMap[event.marketId];
const marketObject = marketDirectory[marketName];
marketObject.fetchClient().then((marketClient)=>{
marketObject.getTime(marketClient).then((result) => {
console.log('<---------------> marker 1 <--------------->');
console.log(result);
});
});
}
SAMPLE C) but this does not:
export async function main(event) {
const marketName = marketIdToNameMap[event.marketId];
const marketObject = marketDirectory[marketName];
const marketClient = await marketObject.fetchClient();
console.log('<---------------> marker 1 <--------------->');
marketObject.getTime(marketClient).then((result) => {
console.log('<---------------> marker 22 <--------------->');
console.log(result);
});
}
the guts of getTime are for all examples:
function getTime(marketClient){
return new Promise((resolve, reject) => {
return marketClient.getTime((err, result) => {
if (err) {
reject(err);
}
resolve(result);
});
}).catch(err => {
throw err;
});
}
clearly, it seems to be an issue with mixing async/awaits with classic promise then-ables. I would expect SAMPLE C to work because getTime() is returning a promise. However, the code simply finishes silently, never hitting the second marker. I have to put the first marker there just to be sure any code is run at all. It feels like i should be able to mix async/await and thenables, but i must not be considering something here.
#adrian, nope
You're neither awaiting nor returning the promise from marketObject.getTime().then(), and this will result in that promise chain executing independently, the main function returning and the process closing. Remember.. then returns a promise too.
the solution is
await marketObject.getTime(marketClient).then(...
or
return marketObject.getTime(marketClient).then(...
either way chains the promise to the main function such that whatever executes it consistently waits for all promises to resolve (or reject).
I suspect Sample B works because the main is not async and Lambda will wait for the event-loop to complete. i.e. it will execute the promise chain even though main returned early.
https://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-handler.html
If you don't use callback in your code, AWS Lambda will call it
implicitly and the return value is null. When the callback is called,
AWS Lambda continues the Lambda function invocation until the event
loop is empty.
... and I suspect if you return a Promise (as you do in Sample C) then Lambda will simply terminate the process immediately once it resolves, which is does because you don't await/return the .then() chain, and thus the floating promise chain you've created will not execute.
I have this little program that calculates totals by multiplying a rate and some hours.
The problem I am having is that the function getTasks() always return an empty map.
When I log the fields being entered in the map, they are not empty but they are entered after the function returns the map.
So I am a bit confused why this is happening.
function getTask(taskId) {
return rp({
uri: `${url}/tasks/${taskId}`,
auth: {
user: 'user',
password,
sendImmediately: false
},
json: true
}).then((task) => {
return task;
});
}
function getTasks(entries) {
const taskIds = [];
entries.forEach((entry) => {
const taskId = entry.day_entry.task_id;
if (!taskIds.includes(taskId)) {
taskIds.push(taskId);
}
});
const taskRateMap = new Map();
taskIds.forEach((taskId) => {
return Promise.resolve(getTask(taskId)).then((res) => {
if (res.task.id === taskId) {
taskRateMap.set(taskId, res.task.default_hourly_rate);
console.log(taskRateMap);
}
});
});
return taskRateMap;
}
function calculateTasksTotals(id) {
return co(function* calculateTotalsGen() {
let total = 0;
const entries = yield getEntriesForProject(id);
const tasks = getTasks(entries);
entries.forEach((entry) => {
const rate = tasks.get(entry.day_entry.task_id);
total += rate * entry.day_entry.hours;
});
return total;
});
}
calculateTasksTotals(id)
There are multiple problems with your code:
First off, as soon as you have any asynchronous operation involved in a function, the result of that function is going to be asynchronous. You simply cannot return it synchronously. The async operation finishes sometime later. The function itself returns BEFORE the async operation finishes.
So, you return a promise from any function that uses an async operation and the caller uses that promise to know when things are done or to get the final result.
Your function getTask() is fine. It returns a promise. The .then() inside that function is redundant and not needed since task appears to already be the resolved value of the promise.
Your function getTasks() is trying to synchronously return taskRateMap, but as you've seen in testing, none of the promises have resolved yet so there are no values in taskRateMap yet. In my code version, I use Promise.all() internally to know when all the getTask() operations are done and I return a promise who's resolved value is the taskRateMap object.
The caller of getTasks() can then use that promise and a .then() handler to get access to the taskRateMap object.
Here's one way to implement getTasks():
function getTasks(entries) {
// get all unique task_id values from the entries array
const taskIds = Array.from(new Set(entries.map(entry => entry.day_entry.task_id)));
const taskRateMap = new Map();
// use Promise.all() to know when the whole array of promises is done
// use tasksIds.map() to build an array of promises
return Promise.all(taskIds.map(taskId => {
// make this promise be the return value inside the .map() callback
// so we will end up with an array of promises that will be passed to
// Promise.all()
return getTask(taskId).then(res => {
if (res.task.id === taskId) {
taskRateMap.set(taskId, res.task.default_hourly_rate);
}
})
})).then(() => {
// make final resolved value be the taskRateMap
return taskRateMap;
});
}
getTasks(entries).then(taskRateMap => {
// use taskRateMap here
});
You have an issue with your Promises. Try using Promise.all() providing all the promises as input, and return your map only when all the promises have been resolved.
Otherwise directly return your Promise.all() and create the map in the calling method.
So something like:
const tasksPromises = [];
taskIds.forEach((taskId) => {
tasksPromises.push(getTask(taskId));
});
return Promise.all(tasksPromises);
Then inside your calling method resolve the promises through then and you will have as parameter of the callback function an array where each element is the returned value of the corresponding promise.
I believe this happening because the taskRateMap isn't being populated before it's being returned. You might want to look into Promise.all()
and consider wrapping
promises = taskIds.map((taskId) => {
return Promise.resolve(getTask(taskId)).then((res) => {
if (res.task.id === taskId) {
return [taskId, res.task.default_hourly_rate];
console.log(taskRateMap);
}
});
return Promise.all(promises).then(v => /* taskRateMap*/)