async, await, and keeping your promises 
Tags: javascript, typescript, webdev
Published: 2020-05-15 03:58:15

The `async function` and `await` keyword introduced as part of ECMAScript 2017 do provide very useful syntactic sugar on top of promises. `Promise` in itself provides alternative to callbacks when writing asynchronous code. Promises can be chained, its inbuilt methods like `all`, `any`, and `race` helps to manages multiple asynchronous tasks.

Check the following example, here `getData` function mimics asynchronous behavior. In real world, you can think it as your data layer, using functions like `fetch` or a 3rd party library which still using callbacks for asynchronous programming.

```TypeScript
const getData = (n: number) => {
    return new Promise<number>((res, rej) => {
        if (n === 3) {
            rej('Can not use 3.');
            return;
        }
        res(n * n);
    });
}
```

If I have to, fetch data for 2, and based on that response fetch data for 3 and 4, then the code will look like something below.

```TypeScript
const check = () => {
    getData(2)
        .then(x2 => {
            console.log(x2);
            return getData(3);
        })
        .then(x3 => {
            console.log(x3);
            return getData(4);
        })
        .then(x4 => {
            console.log(x4);
        }).catch((ex) => { // This is catch handler
            console.log('Error occurred : Check with Promise.');
            console.log(ex);
        });
}
```

If we used async and await, same code will be more readable and easy to understand.

```TypeScript
const check = async () => {
    try {
        const x2: number = await getData(2);
        console.log(x2);
        const x3: number = await getData(3);
        console.log(x3);
        const x4: number = await getData(4);
        console.log(x4);
    } catch (ex) { // This is catch block
        console.log('error occurred : check with async and await.');
        console.log(ex);
    }
}
```
 
Error handling, is still a challenge. If a promise is rejected, then either the catch handler get executed, or an exception will be thrown. With `await` keywords, only way to handle rejected promise is `try-catch` block.

This may work for some cases, but what if you are fine with errors while loading data for 3 and 4. The catch block does not give a good way to handle control flow. You may end up having separate `try-catch` blocks for each `await`, and that will worsen the problem.

Languages like `go`, has a different philosophy to handle errors. It segregates `error` from `exception`, and communicate errors with ordinary values as return parameters.
Lets see what happens when we try that philosophy here.

> Always keep your promises

Let change the getData function so that it can never reject the promise. The promise will be always resolved, and errors will be reported via a return type.

```TypeScript
type PromiseResponse<T> = Promise<[string] | [null, T]>;

const getData = (n: number) : PromiseResponse<number> => {
    return new Promise((res) => {
        if (n === 3) {
            // no reject here 
            res(['Can not use 3.']);
            return;
        }
        res([null, n * n]);
    });
}
```

I have declared a Type here `PromiseResponse`, which is a Promise returning tuple, and will assist TypeScript for better syntax checking.
- First item will be error : string or null.
- Second item will be actual result of Type T or undefined.

```TypeScript
const check3 = async () => {
    const [e2, x2] = await getDataV2(2);
    // Here for TypeScript x2 is either number or undefined
    if (x2 === undefined) {
        console.log('Error while fetching data for 2');
        return;
    }
    // As x2 is checked for undefined
    // at this line x2 is number
    console.log(x2);

    // now fetch data for 3 and 4
    const [e3, x3] = await getDataV2(3);
    if (x3 !== undefined) {
        console.log(x3);
    }
    
    const [e4, x4] = await getDataV2(4);
    if (x4 !== undefined) {
        console.log(x4);
    }
}
```

With the new approach, code don't need to use `try-catch` block and we have better control over flow.

I am using this techniques for application layer, which seats between UI and underlying data, and its make life much easier.

Based on your requirements, you can extend the Type `PromiseResponse` into a class, and with helper methods like `Success` and `Error` to make your code more readable.

I have a utility function, named as `aKeptPromise` on propose. With this function getData is more readable.

```TypeScript
function aKeptPromise<T>(
  callback: (
    success: (result: T) => void,
    failure: (error: string) => void
  ) => void
): PromiseResponse<T> {
  return new Promise((res) => {
    callback(
      (r) => res([null, r]),
      (e) => res([e])
    );
  });
}

const getDataV3 = (n: number) : PromiseResponse<number> => {
    return aKeptPromise((success, failure) => {
        if (n === 3) {
            failure('Can not use 3.');
            return;
        }
        success(n * n);
    });
}
```

[TypeScript Playground](https://bit.ly/3dSOssW)

Thanks for reading. Let me know if you have comments.