Pada project yang menggunakan bahasa Typescript atau Javascript modern, penggunaan promise dan sistem asinkronus sudah banyak digunakan secara umum. Fitur asinkron dalam pemrograman memungkinkan eksekusi beberapa tugas secara bersamaan tanpa harus menunggu tugas satu selesai sebelum tugas lainnya dimulai.

Dalam konteks JavaScript dan TypeScript, salah cara utama untuk mengelola operasi asinkron adalah menggunakan Promise. Promise adalah objek yang merepresentasikan nilai yang mungkin akan tersedia di masa depan, atau mungkin tidak tersedia sama sekali. Ide dasar di balik Promise adalah bahwa suatu operasi asinkron dapat memiliki dua hasil: berhasil atau gagal.

Pada artikel ini, akan dibahas konsep untuk error handling dalam kode asinkronus menggunakan TypeScript. Pada implementasinya akan menggunakan async/await untuk membuat kode lebih mudah dibaca dan strategi untuk menangani kesalahan yang muncul selama operasi asinkron.

Kode Error Handling

type TryPromiseReturn<T, U> = Promise<[U, undefined] | [null, T]>;

function tryPromise<T, U extends Error>(promiseHandle: Promise<T>): TryPromiseReturn<T, U> {
    return promiseHandle
        .then<[null, T]>((data: T) => [null, data])
        .catch<[U, undefined]>((err: U) => [err, undefined]);
}

export { tryPromise };

Kode diatas merupakan function di typescript yang digunakan untuk mengatasi error. Pada function diatas didefinisikan tryPromise dengan parameter Promise<T> dengan tipe generic sehingga dapat menerima semua return type promise yang dimasukkan pada parameternya.

Kemudian di dalam function tryPromise dilakukan handling promise seperti pada biasanya menggunakan callback then dan catch, callback then digunakan ketika promise berhasil dijalankan dan tidak ada error di dalamnya. Callback ini menggunakan paramter data dengan tipe generic turunan dari argument Promise<T> sehingga tipenya akan sama dengan return dari hasil promiseHandle.

Jika promise berhasil maka akan dikembalikan keluaran berbentuk array format array dengan hasil kembalian seperti dibawah

→ Kondisi sukses

[error: null, data: ...]

→ Kondisi Error

[error: ..., data:null]

Data merupakan array dengan index ke [1] sedangkan error berada pada array dengan index ke [0] dengan struktur seperti diatas dengan mudah data array dapat dilakukan destructuring menggunakan fungsi destruktur array pada javascript dan dapat langsung disimpan pada variable yang akan digunakan.

Penggunaan Kode

Ada skenario dimana sebuah program melakukan request ke external API untuk mendapatkan data json. Pada proses yang dijalankan banyak sekali error yang akan dihadapi maupun error yang tidak terduga yang dapat terjadi sewaktu waktu seperti:

  • Koneksi gagal sehingga request tidak terkirim
  • HTTP 500 Internal Server Error
  • Gateway Timeout
  • Invalid Json
  • dll

→ Handling error menggunakan Try Catch ❌

import { tryPromise } from "../../lib/utils/helpers";

async function fetchData(query: string): Promise<object | null> {
  const fetchUrl = `/api/data?q=${encodeURIComponent(query)}`;

  let fetchJson = null;

  try {
    const fetchCtx = await fetch(fetchUrl, {
      method: "GET",
      mode: "cors",
      credentials: "include",
      body: null,
    });

    if (!fetchCtx.ok) {
      const fetchStatus = fetchCtx.statusText;
      console.log(fetchStatus);
      return null;
    }
    
    fetchJson = await fetchCtx.json();

  } catch (err) {
    console.log(err);
  }

  return fetchJson;
}

Penggunaan error handling menggunakan try catch hasilnya akan mengcover seluruh blok pada kode. Dengan menggunakan try catch kita dipaksa untuk memasukkan seluruh blok kode di dalam statement tersebut akhirnya kode yang dihasilkan menjadi susah untuk dimaintain dan susah untuk dibaca, karena bisa saja error terjadi di bagian kode mana saja dan tidak ada handling spesifik untuk function tertentu.

→ Handling error menggunakan tryPromise ✅

import { tryPromise } from "../../lib/utils/helpers";

async function fetchData(query: string): Promise<object | null> {
    const fetchUrl = `/api/data?q=${encodeURIComponent(query)}`;

    const [errFetchCtx, fetchCtx] = await tryPromise(
        fetch(fetchUrl, {
            method: "GET",
            mode: "cors",
            credentials: "include",
            body: null,
        })
    );

    if (errFetchCtx) {
        console.log("Error when executing fetch ", errFetchCtx);
        return null;
    }

    if (!fetchCtx.ok) {
        const fetchStatus = fetchCtx.statusText;
        console.log(fetchStatus);
        return null;
    }

    const [errFetchJson, fetchJson] = await tryPromise(fetchCtx.json());

    if (errFetchJson) {
        console.log("Error when parsing JSON response");
        return null;
    }

    return fetchJson;
}

Jika function tryPromise digunakan, maka handling error dapat dilakukan pada setiap function yang akan dipanggil. Sehingga kode yang ditulis akan mudah terbaca, selain itu akan terlihat jelas dimana function tersebut dipanggil dan dimana error dari function tersebut akan terjadi.

Sehingga kita tidak perlu memasukkan seluruh blok kode ke dalam function try catch hanya untuk menghandle satu error yang ada pada function, serta kondisi error handling tiap promise dapat berbeda beda dan bisa disesuaikan sesuai kebutuhan apakah error tersebut perlu penanganan lanjutan pada saat pengecekannya.

Penutup

Ada banyak cara untuk menangani error pada program, entah itu error atau tidak karena hanya programmer yang dapat mendefinisikannya. Pada sejatinya error adalah sebuah value / nilai dan sebagai programmer adalah bagaimana kita menangani error tersebut atau mengabaikannya. Untuk solusi tryPromise memang banyak terinsipirasi dari penanganan error dari bahasa Go karena dalam Golang penanganan error merupakan hal yang dibuat secara simple tidak perlu membutuhkan blok penanganan yang lebih lanjut. Error pada golang dihandle layaknya variable adalah sebuah nilai yang di definisikan oleh programmer.

Tidak ada cara yang sepenuhnya benar untuk menangani error, penggunaan try catch akan lebih sesuai pada use case tertentu sehingga penggunaan tryPromise malah akan mempersulit. Oleh karena itu, gunakanlah cara dan metode sesuai kebuhan anda.

Referensi

https://go.dev/blog/error-handling-and-go

https://github.com/scopsy/await-to-js