I'm currently writing a project that will need to fetch data from outside. My approach is when the event of DOMContentLoaded is fired, I will begin to fetch the data. But I'm kind of stuck on whether I should use the event DOMContentLoaded on window object or on document object. What are the differences and which is the better one?
My code below:
async function getProducts() {
try {
const result = await fetch('./products.json');
const data = await result.json();
return data ;
} catch (error) {
console.log(error);
}
}
Should I use this one below:
window.addEventListener('DOMContentLoaded', () => {
getProducts().then(//code that will render out the data on my web page);
});
Or should I use this one below:
document.addEventListener('DOMContentLoaded', () => {
getProducts().then(//code that will render out the data on my web page);
});
What are the differences and which approach is more appropriate?
I've seen this
https://stackoverflow.com/questions/588040/window-onload-vs-document-onload
but I still cannot figure out my question.
Related
I have a facade function that reloads the current firebase user and returns it. The thing is that the user reloading part has a timeout and it needs to be tested.
Function:
Future<Option<User>> getSignedInUser() async {
// Reload currentUser if possible
// it mustn't throw [TimeoutException] for whole function,
// this is what this try/catch does
try {
await reloadCurrentUser().timeout(const Duration(seconds: 20));
} catch (e) {
log(e.toString(), name: TAG);
}
return optionOf(_auth.currentUser);
}
reloadCurrentUser() function:
Future<Either<AuthFailure, Unit>> reloadCurrentUser() async {
try {
await _auth.currentUser?.reload();
return right(unit);
} catch (e) {
log(e.toString(), name: TAG);
return left(const AuthFailure.userReloadingError());
}
}
The question is how to test reloadCurrentUser() timeout? I'm trying to throw a TimeoutException when this function is called, but then it throws an error for the whole test.
Current Test function:
test(
'Reaches timeout when reloading currentUser, '
'throws TimeoutException, but function continues '
'and returns optionOf currentUser', () async {
reset(fakeFirebaseAuth);
reset(fakeFacebookAuth);
reset(fakeGoogleSignIn);
final currentUser = FakeUser();
// It says that currentUser exists and *IS* authenticated
when(() => fakeFirebaseAuth.currentUser).thenReturn(currentUser);
when(() => firebaseAuthFacade.reloadCurrentUser())
.thenThrow(TimeoutException('timeout', const Duration(seconds: 20)));
final result = await firebaseAuthFacade.getSignedInUser();
expect(result, isA<Some<User>>());
});
Maybe it's better to remove timeout and use some connectivity package to ensure that the user has a network connection and only then reload the current user?
For testing I'm using mocktail package.
You can use the fake_async package.
Here's a simple example from their docs that you can modify for your use case:
import 'dart:async';
import 'package:fake_async/fake_async.dart';
import 'package:test/test.dart';
void main() {
test("Future.timeout() throws an error once the timeout is up", () {
// Any code run within [fakeAsync] is run within the context of the
// [FakeAsync] object passed to the callback.
fakeAsync((async) {
// All asynchronous features that rely on timing are automatically
// controlled by [fakeAsync].
expect(Completer().future.timeout(Duration(seconds: 5)),
throwsA(isA<TimeoutException>()));
// This will cause the timeout above to fire immediately, without waiting
// 5 seconds of real time.
async.elapse(Duration(seconds: 5));
});
});
}
I am writing a simple program with React + Redux.
Inside createAsyncThunk, I need to send multiple requests to send data chunks.
Now my codes are something like the bellow;
export const sendData = createAsyncThunk<
void,
{
id: string;
data: Uint8Array;
}
>("files/upload", async (props) => {
const { uploadId } = await api.init();
try {
let i = 0;
while (props.data.length > i * CHUNK_SIZE) {
const chunkedData ... // Here I create chunked data from props.data.
await api.uploadFileChunk({
uploadId: uploadId,
data: chunkedData,
});
i++;
}
await api.complete({
id: props.id,
uploadId: uploadId,
});
} catch (err) {
await api.abort({ uploadId: uploadId });
}
});
Currently, I show a loading bar while sending data but it does not tell the progress. I want to improve the program to show a progress bar because it takes a long time if the data is large to send.
How can I manage progress with createAsyncThunk?
You need to be dispatching some sort of action each time that a chunk is loaded. That way your store is aware of how much has been loaded and can select the progress. The createAsyncThunk function is designed to create three action creators for pending, fulfilled, and rejected.
There is a second argument after props which you have access to in your callback. That is the thunkAPI object which contains your store's dispatch method. You should be able to use this dispatch additional actions from inside your main payloadCreator callback.
Be careful with catch-ing errors here. I'm not sure but I think this will cause a fulfilled response so you would need to use rejectWithValue.
It should look something like this (obviously untested)
export const sendData = createAsyncThunk<
void,
{
id: string;
data: Uint8Array;
}
>(
"files/upload",
async (props, {dispatch, rejectWithValue}) => {
const { uploadId } = await api.init();
try {
let i = 0;
while (props.data.length > i * CHUNK_SIZE) {
const chunkedData = // Here I create chunked data from props.data.
await api.uploadFileChunk({
uploadId: uploadId,
data: chunkedData,
});
i++;
dispatch({
type: "files/uploadProgress",
percent: //calculate this
});
}
await api.complete({
id: props.id,
uploadId: uploadId,
});
} catch (err) {
await api.abort({ uploadId: uploadId });
rejectWithValue(err);
}
});
If I'm reading your loop correctly, I think you want i += CHUNK_SIZE rather than i++ since you upload a bunch at a time. I'm leaving it up to you to figure out the progress calculations.
I am trying to display information about users in a group in a list. I want to gather additional information about each member which is stored under users/ in my database.
My code manages to gather information but only for the first member. To get the others members data to load, I need to navigate back to a different screen then go back.
Here is what I tried so far:
//members object from group/id/members is passed to this function
//e.g. [{"uid":"abc","type":"admin"},{"uid":"def","type":"member"},{"uid":"ghi","type":"member"}]
getMembersData(members) {
const membersData = {};
Object.keys(members).map((key, i) => {
const member = members[key];
firebase.database().ref(`users/${member.uid}`).on('value', snap => {
membersData[member.uid] = {
'uid': member.uid,
'username': snap.child('username').val(),
'imageUrl': snap.child('imageUrl').val(),
'type': member.type,
};
});
});
this.setState({ membersData });
}
In other words, when I navigate to the members screen, only the information for the first member is rendered when I map this.state.membersData in a View, until I back out from the screen and go back to it.
The code works fine if I cut out the firebase content. How can I fix this issue so that everything loads when I navigate to the screen?
I think you'll want to call setState inside the database callback since the callback is asynchronous and won't be invoked until the data is finally available.
firebase.database().ref(`users/${member.uid}`).on('value', snap => {
membersData[member.uid] = {
'uid': member.uid,
'username': snap.child('username').val(),
'imageUrl': snap.child('imageUrl').val(),
'type': member.type,
};
this.setState(membersData);
});
});
There is no guarantee how long it will take to get the data, so your code should be ready to deal with the fact that it might take a while to arrive (or possibly never at all if there is no internet connection).
You want to call this.setState once all of your database calls have finished. If you only need to download the data once, you can make use of chaining Promises.
Because you are using .map(), you can achieve this by returning the Promise from database requests that use .once('value'). In the code below, the variable named transformPromises is the array of Promises produced by .map() and .once('value'). Once you have this array of promises, you can use Promise.all(promiseArray).then(() => {...} to wait for them all to resolve. In the code below, this is where we then call this.setState() to update the user interface.
Before calling getMembersData() it may be useful to show a throbber while loading the data.
getMembersData(members) {
const membersData = {};
let transformPromises = Object.keys(members).map((key, i) => {
const member = members[key];
return firebase.database().ref(`users/${member.uid}`).once('value')
.then(snap => {
membersData[member.uid] = {
'uid': member.uid,
'username': snap.child('username').val(),
'imageUrl': snap.child('imageUrl').val(),
'type': member.type,
};
});
});
Promise.all(transformPromises)
.then(() => {
// all data was downloaded & added successfully.
this.setState({ membersData });
})
.catch((err) => {
// handle error
console.error(err)
});
}
Note: Based on the shape of members, you could also iterate them using the following code:
let transformPromises = members.map(member => {
return firebase.database().ref(`users/${member.uid}`).once('value')
.then(snap => {
membersData[member.uid] = {
'uid': member.uid,
'username': snap.child('username').val(),
'imageUrl': snap.child('imageUrl').val(),
'type': member.type,
};
});
}
I am implementing a command/response pattern where the user writes to a command collection by calling add with a payload under his own userId, and then gets the data from a similar response path.
However the code below doesn't work, because onSnapshot can not listen for a document that hasn't yet been created (document command.id in the /responses/{userId}/register collection). This would be easy to solve with an onCreate handler, which exists for cloud functions but not for the JS firebase client API it seems.
This is using redux-firestore and some of my app helper functions, but you'll get the idea. The command and response document structures use { payload, error} similar to FSA
Cloud Function
export const register = functions.firestore
.document("commands/{userId}/register/{commandId}")
.onCreate(async event => {
const payload = event.data.get("payload");
const { userId, commandId } = event.params;
const response = db.document(`responses/${userId}/register/${commandId}`)
// possibly something bad will happen
try {
// do something with payload...
return response.set({
payload: "ok" // or pass relevant response data
})
} catch(err) {
return response.set({
error: true
payload: error
})
}
});
Client
export async function register(fs: any, userId: string) {
try {
// issue a new command
const command = await fs.add(
{ collection: `commands/${userId}/register` },
{ payload: fs.firestore.FieldValue.serverTimestamp() }
);
// wait for the response to be created
fs.onSnapshot(
{ collection: `responses/${userId}/register`, doc: command.id },
function onNext(doc) {
const {error, payload} = doc.data()
if (error) {
return notify.error({ title: 'Failed to register', message: payload.message });
}
notify.json(payload);
},
function onError(err) {
notify.error(err);
}
);
} catch (err) {
notify.error(err);
}
}
Is there no such thing as onCreate for web clients?
The only scalable solution I can think of is to store the response data as a child in the command document, but I think it is not as nice, because I suspect you can not make the permissions as strict then.
I would like the user only to be able to write to the command, and only read from the response paths. If I place the response as a child of command, this would not be possible I think?
I'm wondering if I'm not overlooking some API...
I'm trying to generate tests dynamically by looping over an array returned from an async call. I just cannot figure out how to do this - either using mocha or using jest. To illustrate using code, the following synchronous example works:
describe("Test using various frameworks", () => {
["mocha", "jest"].forEach(framework => {
it(`Should test using ${framework}`, () => {
expect(true).toBe(true);
});
});
});
However, if that array is fetched asynchronously, I cannot get the testing frameworks to wait until the array is fetched before trying to loop over it.
async function getFrameworks() {
//TODO: get it from some async source here
return ["mocha", "jest"];
}
describe("Test using various frameworks", () => {
var frameworks;
//before() instead of beforeAll() if using mocha
beforeAll(async ()=> {
frameworks = await getFrameworks();
});
frameworks.forEach(framework => {
it(`Should test using ${framework}`, () => {
expect(true).toBe(true);
});
});
});
This fails saying Cannot read property 'forEach' of undefined. I've tried all sort of combinations of using async/await and Promise and passing in a done callback but to no avail.
The closest I came to this was using Mocha's --delay flag, but that only solves part of the problem. What I really want to do in my actual use case is to run some async intialization in the before() or beforeAll() hooks which I then use to dynamically generate tests.
Any pointers on how to do this using either mocha or jest?
To answer my own question, I did not find a way to do this using Jest or Mocha, but I could accomplish it using tap - which I used through babel-tap.
import tap from "babel-tap";
async function getFrameworks() {
//TODO: get it from some async source here
return ["mocha", "jest"];
}
getFrameworks().then(frameworks => {
frameworks.forEach(framework => {
tap.test(`Should test using ${framework}`, (tester) => {
tester.ok("It works!");
});
});
});
You can do a lot more though. You can create nested scopes by further calling tester.test() for example. Also, since tap does not have the concept of before, after etc, (unless you use the Mocha-like DSL ), you can simply use imperative code to simulate the equivalent behavior.
Also, you can freely use async/await style calls inside tests.
Mocha has some support async tests. Specifically, your it() callback can be async. Here is how to rearrange your tests to "looping over an array returned from an async call":
const chai = require('chai');
const { expect } = require('chai');
function getMyTestData() {
return new Promise(resolve => setTimeout(() => resolve(['mocha', 'jest']), 1000));
}
describe('Test using various frameworks', () => {
it('Should test by looping over an array returned from an async call', async () => {
const myTestData = await getMyTestData();
for(let datum of myTestData) {
expect(datum.length).greaterThan(4, `Data '${datum}' not long enough`);
}
})
});