How to use a few dispatch in nuxt fetch? - promise

I create a site in nuxt and got data from worpdress api.
I have a few store: home.js, solutions.js, tipo.js, portfolio.js and options.js.
In fetch i check, if the store array is empty, than call dispatch and fill arrays.
export default {
async fetch({ store }) {
try {
if (store.getters['home/home'].length === 0) {
await store.dispatch('home/fetchHome');
}
if (store.getters["solutions/getSolutions"].length === 0) {
await store.dispatch('solutions/fetchSolutions');
}
if (store.getters["tipo/getTipo"].length === 0) {
await store.dispatch('tipo/fetchTipo');
}
if (store.getters["portfolio/getPortfolio"].length === 0) {
await store.dispatch('portfolio/fetchPortfolio');
}
if(store.getters["options/getOptions"].length === 0){
await store.dispatch('options/fetchOptions');
}
} catch (e) {
console.log(e, 'e no data')
}
},
components: { HomeContacts, PortofolioSlider, Clients, ChiSiamo, Solutions, HomeIntro }
}
But the problem is, that the page is loading to long time. Because i call dispatches throw await, and i think, this is the problem.
How can i call all dispatches in fethc, without async, but parallel?
I see the advantage of working with fetch over asyncData in that only the first time when I load the page, I need to wait a little, the arrays will fill up and when I get to the current page from another page, there will be no requests through the api, and the data will be output from the store.
It's just that there is very little information on nuxt in terms of ideology, how to work and what is better to use and when. In next, this is better.
This method doesn't work.
fetch({ store }) {
const promise1 = new Promise((resolve, reject) => {
if (store.getters['home/home'].length === 0) {
resolve(store.dispatch('home/fetchHome'));
}
});
const promise2 = new Promise((resolve, reject) => {
if (store.getters["solutions/getSolutions"].length === 0) {
resolve(store.dispatch('solutions/fetchSolutions'));
}
});
const promise3 = new Promise((resolve, reject) => {
if (store.getters["tipo/getTipo"].length === 0) {
resolve(store.dispatch('tipo/fetchTipo'));
}
});
const promise4 = new Promise((resolve, reject) => {
if (store.getters["portfolio/getPortfolio"].length === 0) {
resolve(store.dispatch('portfolio/fetchPortfolio'));
}
});
const promise5 = new Promise((resolve, reject) => {
if (store.getters["options/getOptions"].length === 0) {
resolve(store.dispatch('options/fetchOptions'));
}
});
Promise.all([promise1, promise2, promise3, promise4, promise5])
.then((data) => console.log(data))
.catch((error) => console.log(error));

Assuming that:
store.dispatch() returns Promise,
the first attempt in the question is generally correct,
the objective is to perform relevant dispatches in parallel,
then:
elimitate await from the store.dispatch() sequence,
accumulate the promises returned by store.dispatch() in an array,
don't use a new Promise() wrapper,
await the Promise returned by Promise.all(promises).
export default {
async fetch({ store }) {
try {
let promises = [];
if (store.getters['home/home'].length === 0) {
promises.push(store.dispatch('home/fetchHome'));
}
if (store.getters['solutions/getSolutions'].length === 0) {
promises.push(store.dispatch('solutions/fetchSolutions'));
}
if (store.getters['tipo/getTipo'].length === 0) {
promises.push(store.dispatch('tipo/fetchTipo'));
}
if (store.getters['portfolio/getPortfolio'].length === 0) {
promises.push(store.dispatch('portfolio/fetchPortfolio'));
}
if(store.getters['options/getOptions'].length === 0) {
promises.push(store.dispatch('options/fetchOptions'));
}
let data = await Promise.all(promises);
console.log(data);
} catch (e) {
console.log(e);
}
},
components: { HomeContacts, PortofolioSlider, Clients, ChiSiamo, Solutions, HomeIntro }
}
For convenience, this can be proceduralised as follows:
export default {
async fetch({ store }) {
try {
let paths = [
{ get: 'home/home', fetch: 'home/fetchHome' },
{ get: 'solutions/getSolutions', fetch: 'solutions/fetchSolutions' },
{ get: 'tipo/getTipo', fetch: 'tipo/fetchTipo' },
{ get: 'portfolio/getPortfolio', fetch: 'portfolio/fetchPortfolio' },
{ get: 'options/getOptions', fetch: 'options/fetchOptions' }
];
let promises = paths.filter(p => store.getters[p.get].length === 0).map(p => store.dispatch(p.fetch));
let data = await Promise.all(promises);
console.log(data);
} catch (e) {
console.log(e);
}
},
components: { HomeContacts, PortofolioSlider, Clients, ChiSiamo, Solutions, HomeIntro }
}
It may make more sense to define the paths array elsewhere in the code and pass it to a simplified fetch(), giving it the profile :
fetch({ store, paths })
If it still doesn't work, then there's something your're not telling us.

Promise.all can be useful here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
Or even Promise.allSettled(), depending on what you're trying to do: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled
Then, this is a matter of displaying something while your page does the fetching. You could use a v-if="$fetchState.pending" at the top level of your page to display a loader while the whole thing is being fetched.
There is nothing related to ideology here, there are 2 hooks that do data fetching by either blocking the render of the page (asyncData()) or allowing you to render it while the data is fetched (fetch()).
Nothing related to the framework by itself, you're free to do as you'd like.

Related

Providing two combined Reducers for my redux saga store prevents my websocket channel message from triggering, but only one does not?

Configured my store this way with redux toolkit for sure
const rootReducer = combineReducers({
someReducer,
systemsConfigs
});
const store = return configureStore({
devTools: true,
reducer: rootReducer ,
// middleware: [middleware, logger],
middleware: (getDefaultMiddleware) => getDefaultMiddleware({ thunk: false }).concat(middleware),
});
middleware.run(sagaRoot)
And thats my channel i am connecting to it
export function createSocketChannel(
productId: ProductId,
pair: string,
createSocket = () => new WebSocket('wss://somewebsocket')
) {
return eventChannel<SocketEvent>((emitter) => {
const socket_OrderBook = createSocket();
socket_OrderBook.addEventListener('open', () => {
emitter({
type: 'connection-established',
payload: true,
});
socket_OrderBook.send(
`subscribe-asdqwe`
);
});
socket_OrderBook.addEventListener('message', (event) => {
if (event.data?.includes('bids')) {
emitter({
type: 'message',
payload: JSON.parse(event.data),
});
//
}
});
socket_OrderBook.addEventListener('close', (event: any) => {
emitter(new SocketClosedByServer());
});
return () => {
if (socket_OrderBook.readyState === WebSocket.OPEN) {
socket_OrderBook.send(
`unsubscribe-order-book-${pair}`
);
}
if (socket_OrderBook.readyState === WebSocket.OPEN || socket_OrderBook.readyState === WebSocket.CONNECTING) {
socket_OrderBook.close();
}
};
}, buffers.expanding<SocketEvent>());
}
And here's how my saga connecting handlers looks like
export function* handleConnectingSocket(ctx: SagaContext) {
try {
const productId = yield select((state: State) => state.productId);
const requested_pair = yield select((state: State) => state.requested_pair);
if (ctx.socketChannel === null) {
ctx.socketChannel = yield call(createSocketChannel, productId, requested_pair);
}
//
const message: SocketEvent = yield take(ctx.socketChannel!);
if (message.type !== 'connection-established') {
throw new SocketUnexpectedResponseError();
}
yield put(connectedSocket());
} catch (error: any) {
reportError(error);
yield put(
disconnectedSocket({
reason: SocketStateReasons.BAD_CONNECTION,
})
);
}
}
export function* handleConnectedSocket(ctx: SagaContext) {
try {
while (true) {
if (ctx.socketChannel === null) {
break;
}
const events = yield flush(ctx.socketChannel);
const startedExecutingAt = performance.now();
if (Array.isArray(events)) {
const deltas = events.reduce(
(patch, event) => {
if (event.type === 'message') {
patch.bids.push(...event.payload.data?.bids);
patch.asks.push(...event.payload.data?.asks);
//
}
//
return patch;
},
{ bids: [], asks: [] } as SocketMessage
);
if (deltas.bids.length || deltas.asks.length) {
yield putResolve(receivedDeltas(deltas));
}
}
yield call(delayNextDispatch, startedExecutingAt);
}
} catch (error: any) {
reportError(error);
yield put(
disconnectedSocket({
reason: SocketStateReasons.UNKNOWN,
})
);
}
}
After Debugging I got the following:
The Thing is that when I Provide one Reducer to my store the channel works well and data is fetched where as when providing combinedReducers I am getting
an established connection from my handleConnectingSocket generator function
and an empty event array [] from
const events = yield flush(ctx.socketChannel) written in handleConnectedSocket
Tried to clarify as much as possible
ok so I start refactoring my typescript by changing the types, then saw all the places that break, there was a problem in my sagas.tsx.
Ping me if someone faced such an issue in the future

Uncaught SyntaxError: await is only valid in async functions and the top level bodies of modules

const accounts = await web3.eth.getAccounts();
App = {
load: async () => {
await App.loadWeb3(
await App.loadAccount()
)
},
loadWeb3: async () => {
if (typeof web3 !== 'undefined') {
App.web3Provider = web3.currentProvider
web3 = new Web3(web3.currentProvider)
} else {
window.alert("Please connect to Metamask.")
}
// Modern dapp browsers...
if (window.ethereum) {
window.web3 = new Web3(ethereum)
try {
// Request account access if needed
await ethereum.enable()
// Acccounts now exposed
web3.eth.sendTransaction({/* ... */})
} catch (error) {
// User denied account access...
}
}
// Legacy dapp browsers...
else if (window.web3) {
App.web3Provider = web3.currentProvider
window.web3 = new Web3(web3.currentProvider)
// Acccounts always exposed
web3.eth.sendTransaction({/* ... */})
}
// Non-dapp browsers...
else {
console.log('Non-Ethereum browser detected. You should consider trying MetaMask!')
}
},
loadAccount: async () => {
App.account = web3.eth.accounts[0]
console.log(App.account)
}
}
$(() => {
$(window).load(() => {
App.load()
})
})
The error is in LINE 1 where I get the accounts from Ganache but await is valid only for async.
What changes should I make in this code to remove the error? Please help me.
If I remove this line the error says that it cannot access accounts and after this await does not work.
Is there any way to make this piece of code in the form of an ASYNC function?
await calls can only be made in functions marked as async. As you have used await in line 1 it is not wrapped in an async function. You can wrap your code in a async function and then call that function. e.g something like:
const main = async () => { // <- the async wrapper function
const accounts = await web3.eth.getAccounts();
// .... rest of your code
$(() => {
$(window).load(() => {
App.load()
})
})
}
main()
Or if you want to be more advanced and not save the function at all
(async ()=>{
const accounts = await web3.eth.getAccounts();
// .... rest of your code
})() // <- call the function right after declaring it

.pipe(takeUntil) is listening when it is not supposed to

We are using .pipe(takeUntil) in the logincomponent.ts. What I need is, it should get destroyed after successful log in and the user is on the landing page. However, the below snippet is being called even when the user is trying to do other activity and hitting submit on the landing page should load different page but the result of submit button is being overridden and taken back to the landing page.
enter code hereforkJoin({
flag: this.auth
.getEnvironmentSettings('featureEnableQubeScan')
.pipe(take(1)),
prefs: this.auth.preferences.pipe(take(1)),
}).subscribe(
(result: any) => {
this.qubeScanEnabled = result.flag.featureEnableQubeScan;
this.userPrefs = result.prefs;
// check to see if we're authed (but don't keep listening)
this.auth.authed
.pipe(takeUntilComponentDestroyed(this))
.subscribe((payload: IJwtPayload) => {
if (payload) {
this.auth.accountO
.pipe(takeUntilComponentDestroyed(this))
.subscribe((account: IAccount) => {
if (this.returnUrl) {
this.router.navigateByUrl(this.returnUrl);
} else {
this.router.navigate(['dashboard']);
}
}
}
}
}
);
ngOnDestroy() {}
Custom Code:
export function takeUntilComponentDestroyed(component: OnDestroy) {
const componentDestroyed = (comp: OnDestroy) => {
const oldNgOnDestroy = comp.ngOnDestroy;
const destroyed$ = new ReplaySubject<void>(1);
comp.ngOnDestroy = () => {
oldNgOnDestroy.apply(comp);
destroyed$.next(undefined);
destroyed$.complete();
};
return destroyed$;
};
return pipe(
takeUntil(componentDestroyed(component))
);
}
Please let me know what I am doing wrong.
Versions:
rxjs: 6.5.5
Angular:10.0.8
Thanks
I've done a first pass at creating a stream that doesn't nest subscriptions and continues to have the same semantics. The major difference is that I can move takeUntilComponentDestroyed to the end of the stream and lets the unsubscibes filter backup the chain. (It's a bit cleaner and you don't run the same code twice every time through)
It's a matter of taste, but flattening operators are a bit easier to follow for many.
enter code hereforkJoin({
flag: this.auth
.getEnvironmentSettings('featureEnableQubeScan')
.pipe(take(1)),
prefs: this.auth.preferences.pipe(take(1)),
}).pipe(
tap((result: any) => {
this.qubeScanEnabled = result.flag.featureEnableQubeScan;
this.userPrefs = result.prefs;
}),
mergeMap((result: any) => this.auth.authed),
filter((payload: IJwtPayload) => payload != null),
mergeMap((payload: IJwtPayload) => this.auth.accountO),
takeUntilComponentDestroyed(this)
).subscribe((account: IAccount) => {
if (this.returnUrl) {
this.router.navigateByUrl(this.returnUrl);
} else {
this.router.navigate(['dashboard']);
}
});
This function doesn't create another inner stream (destroyed$). This way is a bit more back to the basics so it should be easier to debug if you're not getting the result you want.
export function takeUntilComponentDestroyed<T>(comp: OnDestroy): MonoTypeOperatorFunction<T> {
return input$ => new Observable(observer => {
const sub = input$.subscribe({
next: val => observer.next(val),
complete: () => observer.complete(),
error: err => observer.error(err)
});
const oldNgOnDestroy = comp.ngOnDestroy;
comp.ngOnDestroy = () => {
oldNgOnDestroy.apply(comp);
sub.unsubscribe();
observer.complete();
};
return { unsubscribe: () => sub.unsubscribe() };
});
}

Nested dispatch function does not get update props

app.js
const mapStateToProps = (state) => {
return {home:state}
}
const mapDispatchToProps = (dispatch) => {
return {
guestLogin: (data)=>{dispatch(guestLogin(data)).then(()=>{
dispatch(initiateTrans(stateProps.home))
})},
};
}
const mergeProps = (stateProps, dispatchProps, ownProps) => {
return Object.assign({}, ownProps, stateProps, dispatchProps,{
initiateTrans: () => dispatchProps.initiateTrans(stateProps.home),
})
}
Action.js
export const guestLogin= (state)=>{
var data={
'email':state.email,
'name':state.name,
'phone_number':state.ph_number,
'phone_code':state.country_code
}
return function(dispatch) {
return dataservice.guestSignup(data).then(res => {
dispatch(afterLoggedGuest(res))
}).catch(error => {
throw(error);
});
}
}
function afterLoggedGuest(result) {
return {type: guestLoginChange, result};
}
export const initiateTrans= (updatedState)=>{
return function(dispatch) {
return dataservice.initiateTransaction(updatedState).then(res => {
console.log("initiateTransaction",res)
}).catch(error => {
throw(error);
});
}
}
Reducer.js
if(action.type === guestLoginChange){
return {
...state,guestData: {
...state.guestData,
Authorization: action.result.authentication ,
auth_token: action.result.auth_token ,
platform: action.result.platform
} ,
}
}
I am having two api requests.. After first api request success i want to update state value then pass that updated state to another api request..
I tried to get the updted props
how to dispatch the initiateTrans with update props
I need to update value at api request success in call back i need to call one more request with updated state value
currently i am not able to get the update props value
I think this is a good use case for thunk (redux-thunk), which is a middleware that allows you to execute multiple dispatches in an action.
You will need to apply the middleware when you configure the initial store (see docs on link above). But then in your actions, you can wrap the code with a dispatch return statement, which gives you access to multiple calls. For example:
export const guestLogin= (state)=>{
return dispatch => {
var data={...} // some data in here
return dataservice.guestSignup(data).then(res => {
dispatch(afterLoggedGuest(res))
}).catch(error => {
throw(error);
// could dispatch here as well...
});
}
}

SequelizeDatabaseError: could not serialize access due to concurrent update

In Mocha test beforeEach hook, I am trying to destroy all table records.
import { db } from '../src/db/models';
export const truncateTable = () => {
const promises = Object.keys(db).map(key => {
if (key !== 'Sequelize' && key !== 'sequelize') {
console.log(key);
return db[key].destroy({ where: {} });
}
});
return Promise.all(promises);
};
Then in the test, I am doing this:
describe.only('application mutations', () => {
beforeEach(() => truncateTable());
...
The error I am getting:
SequelizeDatabaseError: could not serialize access due to concurrent
update
TL/DR: in your tests, if you want a quick way to delete models and reset your DB, use sync.
describe.only('application mutations', () => {
beforeEach(async () => {
await db.sync({force: true})
});
}
If you want to individually destroy your models, you must properly await for your promise to finish before initiating a new one. Currently, your promises are being initiated all at once, hence the Sequelize error.
export const truncateTable = async () => {
const promises = Object.keys(db).map(key => {
if (key !== 'Sequelize' && key !== 'sequelize') {
await db[key].destroy({ where: {} });
}
});
};
// in your test file
describe.only('application mutations', () => {
beforeEach(async () => {
await truncateTable();
});
})

Resources