How to chain thens in usePromise?
wizardnet972 opened this issue · 5 comments
How to chain .then
s functions and adjust the loading
property to be true after all the thens complete?
I have a function getItems
that return array with ids after 20 seconds (like http).
export function timeout(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
const getItems = () => timeout(20 * 1000).then(() => [{ id: 1 }, { id: 2 }]);
and I call this function via usePromise
:
const useApi = () => {
const api = usePromise(() => getItems(), { lazy: true });
return api;
};
In the setup function I call to useApi and pass the loading
to the template, and I get the r with the data.
const App = {
template: `
<div>
<h1>im app {{loading}}</h1>
</div>
`,
setup() {
const { exec, loading } = useApi();
exec().then(r => {
console.log({ r });
});
return { loading };
}
};
But what if I want to add other function without change the useApi
and I want loading
to not change until all of the then
complete.
exec().then(async r => {
console.log({ r }); // <--- the `loading` is true :(
await timeout(20 * 1000);
// <-- here, now the `loading` should be true.
});
I want to wait another 20 seconds. but after the first then
complete it set loading
to ture
.
@pikax There is something I can do to make it works?
my example on codesandbox.io
When you do .then
it will create a new promise, making the useApi
not be aware of it.
What you can do is passing the function to the exec:
const useApi = () => {
const api = usePromise(p => getItems().then(p), { lazy: true });
return api;
};
//setup
exec(async r => {
console.log({ r, loading });
await timeout(20 * 1000);
});
@pikax thanks for your answer.
But what I think of is to use useApi
as it is. and sometimes I want to do something after, and I want to control it in the host function.
I think about something like this to catch the error or the promise. instead of try..catch..finally .
export const scope = (fn) => (req, res, next, ...args) => Promise.resolve(fn(req, res, next, ...args)).catch(next); // this is how I handle promise with nodejs
This is mean something like Promise/finally:
const promise = new Promise((res) => { setTimeout(() => res(), 10* 1000) });
...
Promise.resolve(promise)
.catch(e => )
.finally(() => loading = false)
...
promise.then(r => ...);
so here you will get the catch when error, and somebody can hook into the original promise. (and the finally always happens)
I don't think you can hook to the original promise.
export function timeout(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
const getItems = () => timeout(1000).then(() => [{ id: 1 }, { id: 2 }]);
function myPromise(p) {
const loading = ref(true);
const promise = Promise.resolve(p).finally(() => (loading.value = false));
return {
promise,
loading
};
}
export default defineComponent({
name: "App",
setup() {
const promise = myPromise(getItems());
promise.promise.then(async x => {
console.log("resolved", x);
await timeout(2000);
console.log("rrr ", promise.loading.value); // this is always false
});
return promise;
}
});
That's a javascript behaviour, if you can provide a working example of hooking into a promised already created I really appreciate it :)
@pikax Here what I suggest. I rewrite the usePromise
(with same signature) but a different way to hook the promise.
What do you think about it? can I send PR?
<template>
<div id="app">Loading: {{loading}}</div>
</template>
<script>
import { defineComponent, toRefs, reactive } from "@vue/composition-api";
export function timeout(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
const getItems = () =>
timeout(1000)
.then(() => {
console.log("in getItems..");
})
.then(() => [{ id: 1 }, { id: 2 }]);
const usePromise = (fnOrPromise, options = { lazy: false }) => {
const state = reactive({
loading: false,
error: null,
promise: typeof fnOrPromise === "function" ? fnOrPromise() : fnOrPromise,
exec: () => {
state.loading = true;
state.error = null;
const executePromise = Promise.resolve(state.promise)
.catch(e => (state.error = e))
.finally(() => {
console.log("in finally");
state.loading = false;
});
return executePromise;
}
});
if (!options.lazy) {
state.exec();
}
return toRefs(state);
};
export default defineComponent({
name: "App",
setup() {
console.clear();
console.log("in setup");
const { exec, loading, promise } = usePromise(() => getItems(), {
lazy: true
});
promise.value.then(r => {
console.log({ r });
console.log({ l: loading.value });
});
exec.value().then(t => {
console.log({ t });
console.log({ l: loading.value });
});
return { loading };
}
});
</script>
That usePromise
actually acts a bit different from what usePromise
currently behaves.
The value is only resolved once, no matter how many times you call exec.value
it will always resolve the first promise (won't call the factory anymore)