vue 异步处理_在Vue 3 / Composition API中处理异步

vue 异步处理

A couple of months ago at work, we’ve decided to go all-in on Composition API with a new version of our product.

几个月前,我们决定使用新版本的产品全面使用Composition API。

And from what I can tell — looking around at new plugins appearing, all the discussions in the discord community — the composition API gets increasingly more popular. It’s not just us.

据我所知-环顾四周出现的新插件,以及不和谐社区中的所有讨论-composition API越来越受欢迎。 不只是我们

Composition API makes a lot of things easier but it also brings some challenges as some things need to be rethought regarding how they fit into this new concept and it might take some time before the best practices are established.

Composition API使很多事情变得更容易,但同时也带来了一些挑战,因为需要重新考虑如何使它们适应这个新概念,并且可能需要一些时间才能建立最佳实践。

One of the challenges is managing state, especially when asynchrony is involved.

挑战之一是管理状态,尤其是在涉及异步时。

I’ll try to showcase different approaches for this, including vue-concurrency, which I’ve created and that I maintain.

我将尝试展示不同的方法,包括我创建并维护的vue-concurrency 。

管理异步状态 (Managing async state)

You might ask what’s there to manage? Composition API is flexible enough, this shouldn’t be a problem.

您可能会问要管理什么? Composition API足够灵活,这不应该成为问题。

The most typical asynchronous operation is doing an AJAX request to the server. In the past years, some generic issues of AJAX have been solved or mitigated. The callback hell was extinguished with Promises and those were later sugared into async/await. Async/await is pretty awesome and the code is a joy to read compared to the original callback hell spaghetti that was often written years before.

最典型的异步操作是对服务器执行AJAX请求。 在过去的几年中,AJAX的一些通用问题已得到解决或缓解。 回调地狱已被Promises扑灭,后来被塞入异步/等待状态。 与多年前编写的原始回调地狱意大利面条相比,Async / await非常棒,并且代码令人愉悦。

But the fact, that things are much better now doesn’t mean that there’s no more space for improvement.

但是事实是,现在情况好得多,并不意味着没有更多的改进空间。

异步功能/承诺不跟踪状态 (Async function / Promises don’t track state)

When you work with a promise, there’s a promise.then , promise.catch ,promise.finallyand that’s it. You cannot accessstatus or someisPending property and other state properties. That’s why you often have to manage this state yourself and with Composition API, your code might look like this:

当您兑现承诺时,就有了一个promise.thenpromise.catchpromise.finally就是这样。 您无法访问status或某些isPending属性和其他状态属性。 这就是为什么您经常不得不自己管理这种状态的原因,并且使用Composition API,您的代码可能如下所示:

import { ref } from 'vue'; 


export default defineComponent({
  setup() {
    const isLoading = ref(false);
    const error = ref(null);
    const data = ref(null);
    const getUsers = async () => {
      isLoading.value = true;
      try {
        const json = await axios('foo/bar');
        data.value = json;
        isLoading.value = false;
      } catch (e) {
        error.value = e;
      }
    };
    
    return { isLoading, error, data, getUsers }; 
  }
});

Here we pass refs like isLoading error data to the template. getUsers function is passed too to allow retrying the operation in case of an error. You might think the code above might still be quite reasonable and in a lot of cases, I’d agree with you. If the asynchronous logic isn’t too complicated, managing state like this is still quite doable.

在这里,我们将诸如isLoading error data类的isLoading传递给模板。 getUsers传递getUsers函数,以允许在发生错误时重试该操作。 您可能会认为上面的代码可能仍然很合理,在很多情况下,我会同意您的观点。 如果异步逻辑不是太复杂,那么像这样管理状态还是很可行的。

Yet I hid one logical mistake in the code above. Can you spot it?

但是我在上面的代码中隐藏了一个逻辑错误。 你能发现吗?

isLoading.value = false; happens only after the successful loading of data but not in case of an error. If the server sends an error response, the view would be stuck in spinner land forever.

isLoading.value = false; 仅在成功加载数据后才会发生,而不会在发生错误的情况下发生。 如果服务器发送错误响应,则视图将永远卡在微调区域。

A trivial mistake but an easy one to make if you’re repeating logic like this over and over again.

如果您要一遍又一遍地重复这样的逻辑,那么这是一个小错误,但是很容易犯。

Eliminating boilerplate code in this case also means eliminating the chance of logical mistakes, typos and so on. Let’s look at different ways how to reduce this:

在这种情况下,消除样板代码还意味着消除逻辑错误,错别字等的机会。 让我们看一下如何减少这种情况:

自定义挂钩:useAsync,usePromise等 (Custom hook: useAsync, usePromise and so on)

You might create your own hook, your own use function that would wrap the logic above. Or you might pick a solution from existing composition API utility libs:

您可以创建自己的钩子,自己的use函数,该函数将包装上面的逻辑。 或者,您可以从现有的composition API实用程序库中选择解决方案:

vue-use — useAsyncState

vue-use — useAsyncState

const { state, ready } = useAsyncState(
axios
.get('https://jsonplaceholder.typicode.com/todos/1')
.then(t => t.data),
{
id: null,
},
)

Pros: simple, accepting plain promise. Cons: no way to retry.

优点:简单,接受明确的承诺。 缺点:无法重试。

vue-composition-toolkit — useAsyncState

vue-composition-toolkit — useAsyncState

const { refData, refError, refState, runAsync } = useAsyncState(() => axios('

Pros: all state is covered. Cons: maybe verbose naming?

优点:涵盖所有状态。 缺点:也许是冗长的命名?

< 悬念 /> (<Suspense />)

Suspense is a new API, originally coming from React land that tackles this problem in a little bit different, quite a unique way.

Suspense是一个新的API,最初来自React领域,以一种与众不同的独特方式解决了这个问题。

If Suspense is about to be used, we can start right away with using async / await directly in the setup function:

如果要使用Suspense,我们可以直接在设置函数中直接使用async / await:

export default defineComponent({
  async setup() {
    const users = await fetchUsers();
    return { users }; 
  }
});
{ { user.name }}

But wait, there’s no being used so far! That’s because it would actually be used in a parent component relative to this one. Suspense effectively observes components in its default slot and can display a fallback content if any of the components promises are not fulfilled:

但是,等等,到目前为止还没有使用 ! 那是因为它实际上将在相对于此组件的父组件中使用。 Suspense有效地观察其默认插槽中的组件,并且如果未满足任何组件承诺,则可以显示后备内容:

In this case waits for promises to be fulfilled both in and . If any promise rejects or if some other error is thrown, it’s captured in the onErrorCaptured hook and set to a ref.

在这种情况下, 等待在都实现诺言。 如果任何承诺被拒绝或引发了其他错误,则将其捕获在onErrorCaptured挂钩中并设置为ref。

This approach has some benefits over the hooks outlined above, because those work via returning ref and therefore in your setup function you have to take into account the possibility that the refs are not filled with data yet:

这种方法比上面概述的钩子有一些好处,因为这些通过返回ref起作用,因此在setup功能中,您必须考虑到ref尚未填充数据的可能性:

setup() {
         
const { refData: response } = useAsyncState(() => ajax('/users');
const users = computed(() => response.value
&& response.value.data.users);
return { users };
}

With TS chaining operator it might become just response.value?.data.users . But still, with you don’t need to deal with ref and you don’t even need a computed in this case!

使用TS链接运算符,它可能仅成为response.value?.data.users 。 但是,即使使用您也不需要处理ref ,在这种情况下甚至不需要computed

const response = await ajax('/users');
const { users } = response.data;
return { users };

Pros:

优点:

  • Plain async / await directly in setup function!

    普通async / await直接在设置功能中async / await

  • no need to use so many ref and computed !

    无需使用那么多的refcomputed

Cons:

缺点:

  • The logic, by design, has to be split into two (or more) components. Error handling and loading view have to be handled in the parent component.

    根据设计,逻辑必须分为两个(或多个)组件。 错误处理和加载视图必须在父组件中进行处理。
  • The fact that data loading is done in a child component and loading / error handling is done in a parent component might be counterintuitive at first

    数据加载是在子组件中完成,而加载/错误处理是在父组件中完成这一事实,一开始可能是违反直觉的
  • Error handling needs to be done via some extra boilerplate code of onErrorCaptured and setting a ref manually.

    错误处理需要通过onErrorCaptured一些额外样板代码并手动设置引用来完成。

  • Suspense is handy for async rendering of data, but might not be ideal let's say for async handling of saving a form, conditionally disabling buttons and so on. A different approach is needed for that.

    暂挂对于数据的异步呈现非常方便,但是对于保存表单,有条件地禁用按钮等的异步处理来说,可能不是理想的选择。 为此需要一种不同的方法。

vue-promised — (vue-promised — )

There’s another approach via a special component: . It is used in a more classical way — it accepts a promise in a prop rather than observing the state of child components as does. Setting up the error and loading views is being done via named slots:

还有一个通过特殊组件的方法: 。 它以更经典的方式使用-它接受道具中的诺言,而不是像那样观察子组件的状态。 设置错误和加载视图是通过命名插槽完成的:

Pros:

优点:

  • Compared to : possibility to have all the data / loading / error views in the same place.

    相比:可以将所有数据/加载/错误视图放在同一位置。

Cons:

缺点:

  • Same as : limited to async rendering, not ideal for other usecases such as submitting a form / toggling state of a button and so on.

    相同:仅限于异步呈现,对于其他用例(例如,提交表单/按钮的切换状态等)不理想。

  • Compared to you might need to use more ofref and computed .

    相比于您可能需要使用更多的refcomputed

vue-concurrency — useTask (vue-concurrency — useTask)

vue-concurrency —a plugin that I’ve created because I wanted to experiement with a new approach in Vue — borrows a well-proven solution from ember-concurrency to tackle these issues. The core concept of vue-concurrency is a Task object which encapsulates an asynchronous operation and holds a bunch of derived reactive state:

vue-concurrency(我创建的插件是因为我想尝试使用Vue中的新方法)-借用了ember-concurrency经过验证的解决方案来解决这些问题。 vue-concurrency的核心概念是一个Task对象,该对象封装了一个异步操作并保存了一堆派生的React状态:

There’s some more specific syntax here compared to the previous solutions, such as perform yield and isRunning , accessing last and so on. vue-concurrency does require a little bit of initial learning. But it should be well worth it. yield in this case behaves the same as await so that it waits for Promise resolution. perform() calls the underlying generator function.

与以前的解决方案相比,这里有一些更具体的语法,例如perform yieldisRunning ,访问last等。 Vue并发确实需要一些初步学习。 但这值得。 在这种情况下, yield行为与await相同,因此它等待Promise解析。 perform()调用基础的生成器函数。

Pros:

优点:

  • The Task is not limited to a template. The reactive state can be used elsewhere.

    任务不限于模板。 React状态可以在其他地方使用。
  • The reactive state on a Task can easily be used for disabling buttons, handling form submissions

    任务上的React状态可以轻松地用于禁用按钮,处理表单提交
  • The Task can always be performed again which allows retrying the operation easily.

    该任务始终可以再次执行,从而可以轻松地重试该操作。
  • Task instance is PromiseLike and so it can be used together with other solutions, such as .

    任务实例是PromiseLike ,因此它可以与其他解决方案(例如一起使用。

  • Tasks scale up well for more complex cases because they offer cancelation and concurrency management — that makes it easy to prevent unwanted behavior and implement techniques like debouncing, throttling, polling.

    任务可以很好地扩展以应对更复杂的情况,因为它们提供了取消和并发管理功能-可以轻松防止不必要的行为并实施反跳,限制,轮询等技术。

Cons:

缺点:

  • Compared to some extra refs and computed might be needed.

    相比,可能需要一些额外的引用并进行计算。

  • A new concept needs to be learned, even if quite minimal.

    即使很少,也需要学习一个新概念。

结论 (Conclusion)

When we deal with async logic we are most likely using some kind of async functions and we deal with Promises. A state that would track running progress, errors, and resolved data then needs to be handled on the side.

当我们处理异步逻辑时,我们很可能会使用某种异步函数,并且会处理Promises。 然后需要在一侧处理跟踪运行进度,错误和已解决数据的状态。

allows eliminating excessive use of ref and computed and allows usage of async/await directly in setup . vue-concurrency brings a concept of a Task that is well flexible to be used in and out of templates and can scale up for more advanced scenarios.

允许消除对refcomputed过度使用,并允许直接在setup使用async/awaitvue-concurrency带来了一种Task的概念,该Task非常灵活,可以在模板内外使用,并且可以扩展到更高级的场景。

下一个 (Up next)

In the next article, I’d like to take a deeper look into another drawback of Promises and how to work around it: lack of cancelation. I’ll show how vue-concurrency solves the issue with generator functions and what benefit it brings, but I’ll also outline other alternatives.

在下一篇文章中,我想更深入地了解Promises的另一个缺点以及解决方法:缺少取消。 我将展示vue-concurrency如何通过生成器函数解决问题以及它带来的好处,但是我还将概述其他选择。

Thanks for reading!

谢谢阅读!

翻译自: https://medium.com/javascript-in-plain-english/handling-asynchrony-in-vue-3-composition-api-part-1-managing-async-state-e993842ebf8f

vue 异步处理

你可能感兴趣的:(vue)