Normal-OJ / new-front-end

v2 client for Normal Online Judge
https://noj.tw
Mozilla Public License 2.0
0 stars 7 forks source link

Repeated pattern in components interacting with models #32

Open uier opened 1 year ago

uier commented 1 year ago

每個有做 POST/PUT/DELETE 的元件都會有這段 pattern 出現,可以討論是否適合包成 hook

至少 catch 那邊的邏輯可以抽出來

https://github.com/Normal-OJ/new-front-end/blob/c77be186bb4a98d0028a7e1f213ab490d7a4824b/src/pages/course/%5Bname%5D/announcements/new.vue#L33-L53

uier commented 1 year ago

for example

async function submit() {
  const [data, error] = await wrapper("my_promise")
}
async function wrapper(myPromise) {
  try {
    const data = await myPromise()
    return [data, null]
  } catch (error) {
    return [null, error?.response?.data?.message || "Unknown error"]
  }
}
uier commented 1 year ago

Related to #42

laporchen commented 1 year ago

我可以試著處理看看這一項

uier commented 1 year ago

@laporchen 上次看這部分有什麼想法嗎?

我覺得初步可以先從抽出 catch (error) 這一塊開始,例如我想到的是改寫成 .then().catch() 然後 catch() 裡放一個 reuse 的 error handling function

laporchen commented 1 year ago

This is what I worked on.

import axios from "axios";

interface RequestHandler<T extends Function> {
  callback: T;
  parameters: T extends (...args: infer U) => unknown ? U : never;
}
function axiosErrorWrapper(error: unknown): string {
  if (axios.isAxiosError(error) && error.response?.data?.message) {
    return error.response.data.message;
  }
  return "Unknown error occurred :(";
}
export async function axiosRequestWrapper<
  F extends Function,
  R extends F extends (...args: unknown[]) => infer R ? R : never,
>(req: RequestHandler<F>): Promise<[null, string] | [R, null]> {
  let res: R;
  try {
    if (req.parameters) {
      res = await req.callback(...req.parameters);
    } else {
      res = req.callback();
    }
  } catch (error) {
    const errorMsg = axiosErrorWrapper(error);
    return [null, errorMsg];
  }
  return [res, null];
}

/* now we can request like this now
const [res, error] = await axiosRequestWrapper({
  callback: () => {
    return { data: "hello" };
  },
  parameters: [],
});

if (error === null) {
  console.log("got res", res.data);
} else {
  console.log("got error :(", error);
}
*/
uier commented 1 year ago

這是個蠻好的包裝,我之前想很久要不要這樣寫,直到我後來想到,我們現在在一個元件裡如果有這樣的 API Call 都會有兩個 ref,一個 isLoading 跟一個 errorMsg,搭配著這個 API Call 用

所以如果要這樣整個包起來(callback, parameters...)我會傾向用 composable (useXXX),然後連 isLoading 跟 errorMsg 都由 composable 提供,簡單來說會長得類似 useAxios

const { isLoading, error, execute } = useAsync(callback: Function)
function submit() {
  if ( isValidForm() ) execute(params);
}
uier commented 1 year ago

總結一下,我傾向寫一個 useAsync 或先只改 error handling 的部分

Bogay commented 1 year ago

最近在學 Nuxt.js,發現它的 useAsyncData 這設計應該就很符合我們的需求?但多了一個 refresh 用來重新抓資料,還有參數有個 watch 可以讓它 reactive,感覺是可以參考下。

附上官方範例:

const { data, pending, error, refresh } = await useAsyncData(
  'mountains',
  () => $fetch('https://api.nuxtjs.dev/mountains')
)