I have a situation with observable like this:
//pseudo code
var agreement:Observable<agreement[]> = Observable.ajax({...})
.flatMap(agreements:[] => {
agreements.forEach(agreement =>{
//server request for each agreement
//If request fail throw exception
});
return agreements;
})
.map(agreements => agreements);
Here, I need to check each agreement if it`s exist in other server and throw exception if not. Main question is how wait while all all agreements are checked adn only then get result.
The answer to this depends on what's happening inside the forEach loop – if agreements (the one going into the flatMap) is an Observable that you're using forEach to emit, the strategy is a bit different altogether because you don't necessarily know how many agreement objects will be emitted.
Assuming though the forEach is just running some other basic http calling, say, one that returns promises... The answer is then going to involve collecting the promises and allowing them all to complete the Promise-way, so that the completion can then be converted into an Observable for the next operator in the chain.
Some pseudo code:
var agreement: Observable<agreement[]> = Observable.ajax({...})
.flatMap(agreements:[] => {
const promises = agreements.map(agreement => doHttpThing()
.then(...)
.catch(err => Observable.throw(err))
);
return Observable.fromPromise(Promise.all(promises));
})
.map(agreements => agreements);
Related
I have two tables with users, where each id for one user is same in both tables (don't ask why I have two user tables).
At some point, I need to filter users from table 1, and if certain condition is true, I store a promise (deleting request) for each user into (let's call it) tableOnePromises. I do the same for table 2.
In order to empty table 2, I MUST first empty table one due to some requirements.
this is what I did:
let tableOnePromises = [];
let tableTwoPromises = [];
tableOne.forEach(item => {
if(item.deactivated) {
const tableOneDeleted = supabase
.from("table-one")
.delete()
.match({id: item.id});
tableOnePromises.push(tableOneDeleted);
const tableTwoDeleted = supabase
.from("table-two")
.delete()
.match({id: item.id});
tableOnePromises.push(tableTwoDeleted);
}
});
await Promise.all(tableOnePromises).then(() => {
return Promise.all(tableTwoPromises)
}).catch(err => console.log(err));
Assuming the code using await is inside an async function (or at the top level of a module), the syntax is correct, but it's probably not what I'd use (in general, avoid mixing async/await with explicit callbacks via .then and .catch), and separately it's probably not working quite as you expect (this is borne out by your saying that your code was failing to delete from table-two).
For any particular id value, your code starts deleting from table-one and then immediately starts deleting from table-two without waiting for the deletion in table-one to complete:
// STARTS the deletion but doesn't wait for it to finish
const tableOneDeleted = supabase
.from("table-one")
.delete()
.match({id: item.id});
// ...
// Starts deleting from `table-two`, even though the item may still be in `table-one`
const tableTwoDeleted = supabase
.from("table-two")
.delete()
.match({id: item.id});
Remember that a promise is just a way of observing an asynchronous process; by the time you have the promise, the process it's observing is already underway.¹ So even though you don't wait for the table-two promises until later, you start the table-two deletions immediately.
...I MUST first empty table one due to some requirements...
If by "empty" you mean just that you have to ensure you've done the delete for a particular id on table-one before doing it on table-two, you need to wait for the table-one deletion to be completed before starting the table-two deletion. I'd put that in a function:
async function deleteItem(id) {
await supabase
.from("table-one")
.delete()
.match({id});
await supabase
.from("table-two")
.delete()
.match({id});
}
Then the code becomes:
const promises = [];
for (const {deactivated, id} of tableOne) {
if (deactivated) {
promises.push(deleteItem(id));
}
}
await Promise.all(promises); // With the `try`/`catch` if desired
...or if it's okay to make two passes through the array:
await Promise.all( // With the `try`/`catch` if desired
tableOne.filter(({deactivated}) => deactivated)
.map(({id}) => deleteItem(id))
);
¹ "...by the time you have the promise, the process it's observing is already underway." That's the normal case. There is unfortunately a popular document DB library that doesn't start its work on something until/unless you call then on the promise for it. But that's an exception, and an anti-pattern.
In JavaScript, I'm calling a promise which returns an id; we'll call this promise1. promise1 has a following .then() which, given the id returned from promise1, calls a for-each loop. On each iteration of this loop, promise2 is carried out
promsie2 also has a .then(). so the data returned from promise2 can be utilized.
mycode.js
exports.my_function = (req, res) => {
var data = req.body;
var name_array = ["Jeff", "Sophie", "Kristen"]
var personal_ID_arr = []
promise1.getID(data) //promise1 gets an int 'id'
.then(id => {
array.forEach(name => { //for each name
promise2.getPersonalID(name, id) //promise2 creates a personal id for each person using their name and the id generated from promise1
.then(personalID => {
personal_ID_arr.push(personalID) //add the generated personal id to an arr
})
})
})
//will need to operate on 'personal_ID_arr' here after the above finishes or possibly still within the promise1's .then; as long as the arr is filled fully
}
However, I'm running into the issue of synchronicity not allowing for this type of logic to happen, as things begin happening out of order once it gets to the loop aspect of the program. I need to carry out another function with the filled-out 'personal_ID_arr' after these promises are carried out as well
I've looked at other questions regarding promise chaining but this is a different case due to the fact the for loop needs to work with the data from the first promise and use .then() inside a .then(). Surely, I don't need to keep this structure though if a better one exists.
Any help would be greatly appreciated
Keeping an array of IDs that an asynchronous operation will fill, is not something that will help you carrying out more asynchronous operations on the array later. Instead, consider using an array of Promises for each name, and wait them all to resolve using Promise.all. Also, i removed the personal_ID_arr from the function my_function variables and moved it to the appropriate asynchronous operation. I did a simple console log of the array, you should fill your next step there.
Although maybe you need to write this logic with Promises, but I would suggest using async / await for this task, it will lead to more readable code.
exports.my_function = (req, res) => {
var data = req.body;
var name_array = ["Jeff", "Sophie", "Kristen"]
promise1.getID(data) //promise1 gets an int 'id'
.then(id =>
//map the array of Strings to array of Promises instead, wait for them to resolve
Promise.all(name_array.map(name =>
promise2.getPersonalID(name, id)
))
)
.then(personal_ID_arr => console.log(personal_ID_arr))
//will need to operate on 'personal_ID_arr' here after the above finishes or possibly still within the promise1's .then; as long as the arr is filled fully
}
That is my first post here. I am not well skilled in asynchronous code so can not resolve the problem by myself
In a React/Redux app I have added cache. The idea behind it is to have something like 'Favorites' functionality but on clients' computer. So, I would like to store over there some data about books. While writing to cache works I can not successively dispatch data to store> Now my code looks like this:
export function fetchFromFavorites() {
return async (dispatch, getState) => {
const URL = getState().books.currentURL;
const Cache = await caches.open(URL);
Cache.matchAll()
.then(function (response) {
const ar = [];
response.forEach(async item => ar.push(await item.json()));
return ar;
})
.then(response => dispatch(test(response)));
};
}
In the code above test is an action that only sets the state field with payload. While the payload can be log-consoled from reducer, I can not perform on that any further action with another external function, well-checked on that kind of data. Besides DevTools mark it with blue 'i' what indicates that it has been calculated very lately. What is wrong with that code? BTW - it has nothing to do with service workers it is just inside regular React.
The function you are passing to response.forEach is returning a promise. You'd need to wait for all of those promises to resolve before returning ar.
For example, you may use something like:
// await all promises to be fulfilled before proceeding.
// note that we use response.map instead of forEach as
// we want to retain a reference to the promise returned
// by the callback.
// Additionally, we can just return the promise returned
// by `item.json()`
await Promise.all(response.map(item => item.json());
Remember, any function marked as async will return a promise wrapping the function's return type.
Note that you're mixing async/await with older style then/catch promises here. For consistency and ease of reading, you may want to use one style consistently.
I have been reading up on methods to implement a polling function and found a great article on https://davidwalsh.name/javascript-polling. Now using a setTimeout rather than setInterval to poll makes a log of sense, especially with an API that I have no control over and has shown to have varying response times.
So I tried to implement such a solution in my own code in order to challenge my understanding of callbacks, promises and the event loop. I have followed guidance outlined in the post to avoid any anti-patterns Is this a "Deferred Antipattern"? and to ensure promise resolution before a .then() promise resolve before inner promise resolved and this is where I am getting stuck. I have put some code together to simulate the scenario so I can highlight the issues.
My hypothetical scenario is this:
I have an API call to a server which responds with a userID. I then use that userID to make a request to another database server which returns a set of data that carries out some machine learning processing that can take several minutes.
Due to the latency, the task is put onto a task queue and once it is complete it updates a NoSql database entry with from isComplete: false to isComplete: true. This means that we then need to poll the database every n seconds until we get a response indicating isComplete: true and then we cease the polling. I understand there are a number of solutions to polling an api but I have yet to see one involving promises, conditional polling, and not following some of the anti-patterns mentioned in the previously linked post. If I have missed anything and this is a repeat I do apologize in advance.
So far the process is outlined by the code below:
let state = false;
const userIdApi = () => {
return new Promise((res, rej) => {
console.log("userIdApi");
const userId = "uid123";
setTimeout(()=> res(userId), 2000)
})
}
const startProcessingTaskApi = userIdApi().then(result => {
return new Promise((res, rej) => {
console.log("startProcessingTaskApi");
const msg = "Task submitted";
setTimeout(()=> res(msg), 2000)
})
})
const pollDatabase = (userId) => {
return new Promise((res, rej) => {
console.log("Polling databse with " + userId)
setTimeout(()=> res(true), 2000)
})
}
Promise.all([userIdApi(), startProcessingTaskApi])
.then(([resultsuserIdApi, resultsStartProcessingTaskApi]) => {
const id = setTimeout(function poll(resultsuserIdApi){
console.log(resultsuserIdApi)
return pollDatabase(resultsuserIdApi)
.then(res=> {
state = res
if (state === true){
clearTimeout(id);
return;
}
setTimeout(poll, 2000, resultsuserIdApi);
})
},2000)
})
I have a question that relates to this code as it is failing to carry out the polling as I need:
I saw in the accepted answer of the post How do I access previous promise results in a .then() chain? that one should "Break the chain" to avoid huge chains of .then() statements. I followed the guidance and it seemed to do the trick (before adding the polling), however, when I console logged out every line it seems that userIdApi is executed twice; once where it is used in the startProcessingTaskApi definition and then in the Promise.all line.
Is this a known occurrence? It makes sense why it happens I am just wondering why this is fine to send two requests to execute the same promise, or if there is a way to perhaps prevent the first request from happening and restrict the function execution to the Promise.all statement?
I am fairly new to Javascript having come from Python so any pointers on where I may be missing some knowledge to be able to get this seemingly simple task working would be greatly appreciated.
I think you're almost there, it seems you're just struggling with the asynchronous nature of javascript. Using promises is definitely the way to go here and understanding how to chain them together is key to implementing your use case.
I would start by implementing a single method that wraps setTimeout to simplify things down.
function delay(millis) {
return new Promise((resolve) => setTimeout(resolve, millis));
}
Then you can re-implement the "API" methods using the delay function.
const userIdApi = () => {
return delay(2000).then(() => "uid123");
};
// Make userId an argument to this method (like pollDatabase) so we don't need to get it twice.
const startProcessingTaskApi = (userId) => {
return delay(2000).then(() => "Task submitted");
};
const pollDatabase = (userId) => {
return delay(2000).then(() => true);
};
You can continue polling the database by simply chaining another promise in the chain when your condition is not met.
function pollUntilComplete(userId) {
return pollDatabase(userId).then((result) => {
if (!result) {
// Result is not ready yet, chain another database polling promise.
return pollUntilComplete(userId);
}
});
}
Then you can put everything together to implement your use case.
userIdApi().then((userId) => {
// Add task processing to the promise chain.
return startProcessingTaskApi(userId).then(() => {
// Add database polling to the promise chain.
return pollUntilComplete(userId);
});
}).then(() => {
// Everything is done at this point.
console.log('done');
}).catch((err) => {
// An error occurred at some point in the promise chain.
console.error(err);
});
This becomes a lot easier if you're able to actually use the async and await keywords.
Using the same delay function as in Jake's answer:
async function doItAll(userID) {
await startTaskProcessingApi(userID);
while (true) {
if (await pollDatabase(userID)) break;
}
}
I'm doing some reading up on JS Promises to up-skill.
Here's my quandry:
Say you want to console.log('we done, bruh!') AFTER your data's come back.
so with a Promise, you might say:
let iWantToLogOut = function() {
let data = fetch('https://jsonplaceholder.typicode.com/users')
return new Promise((resolve) => {
resolve(data)
})
}
And then resolve that promise like:
iWantToLogOut().then((dataBack)
=> databack.json())
.then((json) => {
console.log('We done, bruh! Look: ', json)
})
So that's great. You get your API data back and then we log our msg out.
But isn't it just way easier to go:
let data = fetch('https://jsonplaceholder.typicode.com/users');
data ? console.log('we done, bruh!') : null;
I'm probably over-simplifying/missing something (because... well... i'm retarded) but I just want to make sure i'm really understanding Promises first before i move onto Async/Await.
But isn't it just way easier to go:
let data = fetch('https://jsonplaceholder.typicode.com/users');
data ? console.log('we done, bruh!') : null;
It would be, but it doesn't work. What fetch returns is a promise, not the result of the operation. You can't return the result of an asynchronous process. More: How do I return the response from an asynchronous call?
In the upcoming ES2017 spec, though, we have syntactic sugar around promise consumption which will let you write this:
let data = await fetch('https://jsonplaceholder.typicode.com/users');
// --------^^^^^
console.log('we done, bruh!');
Note we don't even need the conditional, because await converts a promise rejection into an exception.
That code would need to be in an async function, e.g.:
(async function() {
let data = await fetch(/*...*/);
// use data here
})();
The JavaScript engines in some browsers already support async/await, but to use it in the wild, you'll want to transpile with Babel or similar.
Note: You've shown
so with a Promise, you might say:
let iWantToLogOut = function() {
let data = fetch('https://jsonplaceholder.typicode.com/users')
return new Promise((resolve) => {
resolve(data)
})
}
There are a couple of problems with that code:
It never settles the promise you created if the fetch fails.
It calls something data which is not data, it's a promise of data (that's mostly style, but it's misleading).
It exhibits the promise creation anti-pattern. You already have a promise (from fetch), no need to create another.
iWantToLogOut should be simply:
let iWantToLogOut = function() {
return fetch('https://jsonplaceholder.typicode.com/users');
};
That returns a promise that will be resolved with the data, or of course rejected. Which you'd then consume with promise methods or await (within an async function).
It is not a matter of easy.
Usually network calls should be handle asynchronously(I don't want to the anti-pattern of synchronous AJAX calls). At that point you have few options to handle it:
Callbacks
Promises
Observables
In you code above, when it's synchronous, the fetch should return immediately with a promise that will be resolve to the data only when the server has responded. Only then you can check the data for it's content. Further. Because every promise can be fulfilled or failed, in your then you can have a handler for each instead of using the ternary.
From the latest spec:
Synchronous XMLHttpRequest outside of workers is in the process of being removed from the web platform as it has detrimental effects to the end user’s experience. (This is a long process that takes many years.) Developers must not pass false for the async argument when current global object is a Window object. User agents are strongly encouraged to warn about such usage in developer tools and may experiment with throwing an InvalidAccessError exception when it occurs.