Trigger a re-render within a Vue async watch before a blocking operation - vuejs2

I have an async watch that fetches some data from the server. It will batch process the Response in a blocking operation. I am trying to update the view before kicking off the blocking operation, like so:
Vue.component("foo-bar", {
...
watch: {
async things() {
const response = await API.getThings();
this.someUIMessage = `processing ${response.length} things...`;
someBlockingOperation(response);
}
}
}
But this.someUIMessage is not updated in the view until after someBlockingOperation. I stuck an await Vue.nextTick() in between setting the string and calling the blocking op, and this.$forceUpdate(), but without success.
What does work (sometimes! depends on what else is going on in the app) is calling setTimeout(() => someBlockingOperation(response), 0), but that seems like a kludge. Is there a step I'm missing?

You may need to show some more code because your use case is exactly described in the docs - Async Update Queue and await Vue.nextTick() should work
If your someBlockingOperation take too long, it may be worth to think about UI responsiveness anyway and maybe apply a strategy of splitting up you workload into smaller chunks, process only one at a time and use setTimeout(nextBatch, 0) to schedule "next chunk" processing. See this SO question for more details...

Related

Apollo readFragment of optimistic response

What I try to do
I have an app which should work offline.
There is an Item-list. I can add an Item to this list with a mutation.
The update function of the mutation adds the Item to the Item-list. (Optimistic Response)
When I click on an Item, I want to see the details.
My Implementation
Content of Mutation update function:
const queryData = cache.readQuery<{ items: Item[] }>({
query: MY_QUERY,
variables: {
filter
}
});
if (!queryData?.items) {
return;
}
const newData = [...queryData.items, newItem];
cache.writeQuery({
query: MY_QUERY,
data: { items: newData },
variables: {
filter
}
});
Get details of the item in the vue-file:
apolloProvider.clients.defaultClient
.readFragment<Item>({
fragment: ITEM_FRAGMENT,
id:id
});
The problem
Adding the item to the Query-result works fine.
When I try to read the fragment:
I get null for items which were added by the Mutation update function
I get the expected object for items which were fetched from the backend
There is also the optimistic attribute in readFragment, but that doesn't make a difference.
Other observations
When I write and immediately read the fragment in the Mutation update function, I am able to get it.
cache.writeFragment({
fragment: ITEM_FRAGMENT,
data: item,
id: item._id,
});
const data = cache.readFragment({
fragment: ITEM_FRAGMENT,
id: item._id,
});
console.log({ data }); // This logs my item Object
Package versions:
{
"#nuxtjs/apollo": "^4.0.1-rc.3",
"apollo-cache-persist": "^0.1.1",
"nuxt": "^2.0.0",
}
Summary
apollo.readFragement doesn't work for values from an optimistic response.
Maybe someone here has an idea of what I am missing, or a different approach to implement this functionality
To get optimistic values from apollo cache you need to add true as second parameter in your readFragment call.
readFragment(options, optimistic)
(optimistic is false by default.)
In your case:
apolloProvider.clients.defaultClient
.readFragment<Item>({
fragment: ITEM_FRAGMENT,
id:id
}, true);
When creating an optimistic response for a resource that is in the process of being created via a mutation (e.g. your item), we need to assign some kind of temporary id to the optimistic data (see example in the docs). This is because the real resource hasn’t been created yet and we don’t know it’s id.
Given this, to read the optimistic response from the cache we need to use that same temporary id (as well as the __typename). When the mutation completes and we have the real response in the cache the optimistic response is discarded and we can use the real id.
I ran into this recently as I was assigning a temporary id, but not using it to retrieve the optimistic response from the cache to render the updated UI in that brief window where I was waiting for the mutation to complete.

How to make gulp.series wait until files are created on previous tasks

I'm currently using something like this:
gulp.task('js', gulp.series('js-a', 'js-b', 'js-c'));
The task js-c requieres js-a and js-b to be executed first and to generate 2 files than then I combine in js-c.
However, no matter if I use gup-series, the function js-c gets executed before the two files from the previous two tasks are created.
How can I tell gulp series to wait for those?
I've read other related issues but they tend to rely on external scripts to accomplish this.
I manually managed to fix this by adding a setTimeout on js-b, but seems like a hacky solution.
Is there any proper way to accomplish this with gulp.series?
To put you in context, the task js-a looks like this:
var gp_concat = require('gulp-concat');
gulp.task('js-a', function(done) {
gulp.src([
'file1.js',
'file2.js'
])
.pipe(gp_concat('tmp.js'))
.pipe(gulp.dest('./'));
done();
});
And I am now forced to use a timeout to fix this issue:
setTimeout(function(){
done();
}, 500);
done() shouldn't be used in this case and the task should return the stream (see here). Please try this:
var gp_concat = require('gulp-concat');
gulp.task('js-a', function() {
return gulp.src([
'file1.js',
'file2.js'
])
.pipe(gp_concat('tmp.js'))
.pipe(gulp.dest('./'));
});

Redux/saga: How to fire an action (put) inside a callback without channels (use sagas as normal generator functions)

I'm looking for a way to fire an action from inside a callback. I know this is not possible by default, but I'm looking for a way around. Channels are a bad solution in my case (for so far I see it).
The library I use is react-native-ble-plx. In that library, there is a function to start scanning: startDeviceScan(UUIDs, options, listener).
To keep this clean, I want to disconnect the start/stop scan from the listener (hence channels are out of the question).
Before I had this solution:
const onScanChannel = () => eventChannel(emit => {
BleService.startDeviceScan(..., ..., (peripheral) => {
emit(peripheral);
}
);
return () => {BleService.stopScan();}
});
The problem is that this connects the channel with the start and the stop of the scan. Which causes you to connect a lot of sagas because you need to start and stop the scanning from application logic (cancel the channel, setup again, start a new saga to listen to the new channel, etc)
What I had in mind is using sagas as normal generator functions:
const startScanSaga = function* () {
BleService.scan(..., ..., (peripheral) => {
const generator = deviceFoundHandler(peripheral);
generator.next();
generator.next();
});
};
const deviceFoundHandler = function* (peripheral) {
yield put(actions.deviceFound(peripheral));
};
That way, the saga for listening to the found-device-actions can just keep running. But, although the put is executed correctly, no take ever receives the action, which suggests that put does not work without the saga-logic behind the scenes.
Does someone know more about this? Or does someone has an alternative approach to realize this?
I managed to fix the problem by using middleware.run(saga, ...args).
I needed to export the sagaMiddleWare: export const sagaMiddleware = createSagaMiddleware();
import {sagaMiddleware} from '.../file-where-sagaMiddleWare-is-exported';
const startScanSaga = function* () {
BleService.scan((peripheral) => {
sagaMiddleware.run(deviceFoundHandler, peripheral);
});
};
const deviceFoundHandler = function* (peripheral) {
yield put(actions.deviceFound(peripheral));
};
Works like a charm =)

redux-observable return multiple action types

I am using redux-observable and this is what I am trying to do.
When an actiontype of 'APPLY_SHOPPING_LIST' comes in dispatch 'APPLYING_SHOPPING_LIST' and after 5 seconds dispatch 'APPLIED_SHOPPING_LIST'. This is the code that I have come up with so far
const applyingShoppingListSource = action$.ofType('APPLY_SHOPPING_LISTS').mapTo({ type: 'APPLYING_SHOPPING_LISTS' });
const applyingShoppingListSourceOther = action$.ofType('APPLY_SHOPPING_LISTS').mapTo({ type: 'APPLIED_SHOPPING_LISTS' }).delay(5000);
const concatList = applyingShoppingListSource.concat(applyingShoppingListSourceOther);
return concatList;
Now the problem is that only 'APPLYING_SHOPPING_LISTS' gets fired, the 'APPLIED_SHOPPING_LISTS' does not get fired to the reducer at all. Am I missing something here?
Just to add to this, when I used flatMap it worked, given below is the code
return action$.ofType('APPLY_SHOPPING_LISTS')
.flatMap(() => Observable.concat(Observable.of({ type: 'APPLYING_SHOPPING_LISTS' }), Observable.of({ type: 'APPLYING_SHOPPING_LISTS' });
I am confused how this works and the other does not?
There's a couple issues. Since Observables are lazy, your second action$.ofType('APPLY_SHOPPING_LISTS') for applyingShoppingListSourceOther is being concat'd after the first applyingShoppingListSource, so it won't be listening for APPLY_SHOPPING_LISTS until after the first one is has completed, but it will never realistically complete because you're taking all actions that match, forever.
Said another way, your code does this:
Start listening for APPLY_SHOPPING_LISTS and when received map it to APPLYING_SHOPPING_LISTS
When that first Observable completes (it never does) start listening for APPLY_SHOPPING_LISTS again, this time when received map it to APPLIED_SHOPPING_LISTS but wait 5000 ms before emitting it.
You could solve the particular issue of the first not ever completing by using .take(1) or .first() (same thing), but you usually need to write your epics to not ever stop listening so they respond to actions at any time.
I think what you want is this:
const exampleEpic = action$ =>
action$.ofType('APPLY_SHOPPING_LISTS')
.mergeMap(() =>
Observable.of({ type: 'APPLYING_SHOPPING_LISTS' })
.concat(
Observable.of({ type: 'APPLIED_SHOPPING_LISTS' })
.delay(5000)
)
);
I used mergeMap but you may want to use switchMap to cancel any previously pending APPLIED_SHOPPING_LISTS that haven't emitted yet. Your call.

How do you poll for a condition in Intern / Leadfoot (not browser / client side)?

I'm trying to verify that an account was created successfully, but after clicking the submit button, I need to wait until the next page has loaded and verify that the user ended up at the correct URL.
I'm using pollUntil to check the URL client side, but that results in Detected a page unload event; script execution does not work across page loads. in Safari at least. I can add a sleep, but I was wondering if there is a better way.
Questions:
How can you poll on something like this.remote.getCurrentUrl()? Basically I want to do something like this.remote.waitForCurrentUrlToEqual(...), but I'm also curious how to poll on anything from Selenium commands vs using pollUntil which executes code in the remote browser.
I'm checking to see if the user ended up at a protected URL after logging in here. Is there a better way to check this besides polling?
Best practices: do I need to make an assertion with Chai or is it even possible when I'm polling and waiting for stuff as my test? For example, in this case, I'm just trying to poll to make sure we ended up at the right URL within 30 seconds and I don't have an explicit assertion. I'm just assuming the test will fail, but it won't say why. If the best practice is to make an assertion here, how would I do it here or any time I'm using wait?
Here's an example of my code:
'create new account': function() {
return this.remote
// Hidden: populate all account details
.findByClassName('nextButton')
.click()
.end()
.then(pollUntil('return location.pathname === "/protected-page" ? true : null', [], 30000));
}
The pollUntil helper works by running an asynchronous script in the browser to check a condition, so it's not going to work across page loads (because the script disappears when a page loads). One way to poll the current remote URL would be to write a poller that would run as part of your functional test, something like (untested):
function pollUrl(remote, targetUrl, timeout) {
return function () {
var dfd = new Deferred();
var endTime = Number(new Date()) + timeout;
(function poll() {
remote.getCurrentUrl().then(function (url) {
if (url === targetUrl) {
dfd.resolve();
}
else if (Number(new Date()) < endTime) {
setTimeout(poll, 500);
}
else {
var error = new Error('timed out; final url is ' + url);
dfd.reject(error);
}
});
})();
return dfd.promise;
}
}
You could call it as:
.then(pollUrl(this.remote, '/protected-page', 30000))
When you're using something like pollUntil, there's no need (or place) to make an assertion. However, with your own polling function you could have it reject its promise with an informative error.