If you have dreams and dry goods, you can search for [Great Move to the World] on WeChat and pay attention to this Shawanzhi who is still washing dishes in the early hours of the morning.

This article GitHub https://github.com/qq449245884/xiaozhi has been included, there are complete test sites, materials and my series of articles for interviews with first-line manufacturers.

This is the 4th part of "Writing Better Code with Composition", the previous article:

If we get asynchronous code to work correctly, it can greatly simplify our code. But dealing with this extra complexity, especially with composability, can be confusing. This post introduces the await-free async pattern. Here's a way to write asynchronous code in composition without the usual headaches.

Async without await

Writing asynchronous behavior with composition APIs can sometimes be cumbersome. All asynchronous code must be at the end of the setup function after any reactive code. If you don't, it may interfere with your reactivity.

When the setup function runs to a await statement, it returns. Once it returns, the component is mounted and the application continues execution as usual. Any responses defined after await , whether computed , watcher , or whatever, have not been initialized.

This means that a computed property defined after await will not be used by the template in the first place. Instead, it only exists after the async code completes, setup the function has finished executing.

However, there is a way to write asynchronous components that can be used anywhere without these hassles.

 const count = ref(0);
// 这种异步数据获取不会干扰我们的响应式
const { state } = useAsyncState(fetchData());
const doubleCount = computed(() => count * 2);

Implement the async pattern without await

To implement this pattern, we will suspend all reactive values synchronously. Then, whenever the async code completes, those values will be updated asynchronously.

First, we need to get our state ready and return. We will initialize with a value of null because we don't know what that value is yet.

 export default useMyAsyncComposable(promise) {
  const state = ref(null);
  return state;
}

Second, we create a method that waits for our promise and then sets the result to state:

 const execute = async () => {
  state.value = await promise;
}

Whenever this promise returns, it actively updates our state.

Now we just need to add this method to the composition.

 export default useMyAsyncComposable(promise) {
  const state = ref(null);

  // Add in the execute method...
  const execute = async () => {
    state.value = await promise;
  }

  // ...and execute it!
  execute();

  return state;
}

We called the execute function before returning from the useMyAsyncComposable method. However, we did not use the await keyword.

When we stop and wait for the promise in the execute method, the execution flow returns immediately to the useMyAsyncComposable function. It then proceeds to execute the execute() statement and returns from the composable object.

 export default useMyAsyncComposable(promise) {
  const state = ref(null);

  const execute = async () => {
    // 2. 等待 promise 执行完成
    state.value = await promise

    // 5. 一段时间后...
    // Promise 执行完,state 更新
    // execute 执行完成
  }

  // 1. 执行 `execute` 方法
  execute();
  // 3.  await 将控制权返回到这一点上。

  // 4. 返回 state 并继续执行 "setup" 方法
  return state;
}

The promise executes in the background, and since we're not waiting for it, it doesn't interrupt the stream in the setup function. We can place this composable anywhere without compromising responsiveness.

Let's see how some compositions in VueUse implement this pattern.

useAsyncState

useAsyncState lets us execute any async method anywhere and get responsive update results.

 const { state, isLoading } = useAsyncState(fetchData());

When looking at the source code , you can see that it implements this exact pattern, but with more features and better handling of edge cases.

Here is a simplified version of useAsyncState:

 export function useAsyncState(promise, initialState) {
  const state = ref(initialState);
  const isReady = ref(false);
  const isLoading = ref(false);
  const error = ref(undefined);

  async function execute() {
    error.value = undefined;
    isReady.value = false;
    isLoading.value = true;

    try {
      const data = await promise;
      state.value = data;
      isReady.value = true;
    }
    catch (e) {
      error.value = e;
    }

    isLoading.value = false;
  }

  execute();

  return {
    state,
    isReady,
    isLoading,
    error,
  };
}

This composable system also returns isReady which tells us when the data was taken. We also got isLoading and error to keep track of our loading and error states.

Now for another composable, which I think has a fascinating implementation.

useAsyncQueue

If passed to useAsyncQueue an array of promise functions, it will execute each function in order. So, before starting the next task, it will wait for the completion of the previous task. For more flexibility, it passes the result of the previous task as input to the next task.

 const { result } = useAsyncQueue([getFirstPromise, getSecondPromise]);

Here is an example from the official website:

 const getFirstPromise = () => {
  // Create our first promise
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(1000);
    }, 10);
  });
};

const getSecondPromise = (result) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(1000 + result);
    }, 20);
  });
};

const { activeIndex, result } = useAsyncQueue([
  getFirstPromise,
  getSecondPromise
]);

Even though it's executing code asynchronously, we don't need to use await . Even internally, composable programs do not use await . Instead, we execute these promises "in the background" and let the results update reactively.

Let's see how this combination works.

 // 初始一些默认值
const initialResult = Array.from(new Array(tasks.length), () => ({
  state: promiseState.pending,
  data: null,
});

// 将默认值变成响应式
const result = reactive(initialResult);

// 声明一个响应式的下标
const activeIndex = ref(-1);

The main function is supported by a reduce which handles each function one by one

 tasks.reduce((prev, curr) => {
  return prev.then((prevRes) => {
    if (result[activeIndex.value]?.state === promiseState.rejected && interrupt) {
      onFinished();
      return;
    }

    return curr(prevRes).then((currentRes) => {
      updateResult(promiseState.fulfilled, currentRes);
      activeIndex.value === tasks.length - 1 && onFinished();
      return currentRes;
    })
  }).catch((e) => {
    updateResult(promiseState.rejected, e);
    onError();
    return e;
  })
}, Promise.resolve());

The Reduce method is a bit complicated, let's disassemble it and see them one by one:

 tasks.reduce((prev, curr) => {
  // ...
}, Promise.resolve());

Then, start working on each task. This is done by chaining a .then on top of the previous promise. If the promise is rejected, abort early and return.

 if (result[activeIndex.value]?.state === promiseState.rejected && interrupt) {
  onFinished();
  return;
}

If not terminated early, execute the next task, passing the result of the previous promise. We also call the updateResult method to add it to the result array returned by the combination

 return curr(prevRes).then((currentRes) => {
  updateResult(promiseState.fulfilled, currentRes);
  activeIndex.value === tasks.length - 1 && onFinished();
  return currentRes;
});

As you can see, the composable implements the Async Without Await pattern, but the pattern is just a few lines of the whole composable. So it doesn't require a lot of extra work, just remember to put it in place

Summarize

If we use the Async Without Await pattern, we can use async composition more easily. This pattern allows us to put asynchronous code where we want without worrying about breaking responsiveness.

The bugs that may exist in editing cannot be known in real time. In order to solve these bugs afterwards, a lot of time is spent on log debugging. By the way, here is a useful BUG monitoring tool , Fundebug .

Author: Michael Thiessen Translator: Xiaozhi Source: vuemastery

Original: https://www.vuemastery.com/blog/coding-btter-composables-1-of-5

comminicate

If you have dreams and dry goods, you can search for [Great Move to the World] on WeChat and pay attention to this Shawanzhi who is still washing dishes in the early hours of the morning.

This article GitHub https://github.com/qq449245884/xiaozhi has been included, there are complete test sites, materials and my series of articles for interviews with first-line manufacturers.


王大冶
68.1k 声望105k 粉丝