loading...

Asynchronous TypeScript Code

minamarouftawfik profile image Mina Marouf ・4 min read

The synchronous code is implemented sequentially. but Async is parallel and notifies on completion, failure or progress.
Async Types

  • Callbacks
  • Promises
  • Async/Await

1. Callbacks

Common places to use Callbacks

  • Timers and intervals
  • Modal with response
  • Http request

Steps to for Callbacks function

  1. You pass the callback to another function
  2. The function does its work
  3. The function executes your callback
  4. The callback now has the data and start to proceed

Sync Callbacks

here the callback function executed synchronously like when using forEach in the following example

export function syncCallBack(data: string[]) {
  data.forEach((val) => {
    console.log(val);
  })
}

Async Callbacks

you can use setTimeout setInterval to execute callBacks Async.

export function getMessage(timeout: number, showMsgCallBack: (msg: string) => void) {
  setTimeout(() => {
    showMsgCallBack('Data returned');
  }, timeout);
}

getMessage(1000, (msg) => {
  console.log(msg);
})

Callbacks with http request

it is common to use callbacks with Http requests, like the following example, use callbacks with Jquery ajax requests but always try to consider the error callback

const getUserBooks = function (
  userId: number,
  callback: (data: { id: number, name: string }[]) => void,
  callbackError?: (msg?: string) => void
) {
  $.ajax({
    url: `api/users/${userId}/books`,
    type: 'GET',
    dataType: 'json',
    contentType: 'application/json',
    success: function (res: { id: number, name: string }[]) {
      callback(res);
    },
    error: function (msg: string) {
      if (callbackError) {
        callbackError(msg);
      }
    }
  });
}

2. Promises

The promise represents the eventual completion or failure of an async operation and its resulting value.

The Key Terms

  • FulFilled => The promise succeeded, often with resolve or Promise.resolve
  • Rejected => The promise failed, often with reject or Promise.reject
  • Pending => The promise is not fulfilled nor rejected yet.
  • Settled => The promise has either been fulfilled or rejected.

Promise constructor

let promise = new Promise<User[]>((resolve, reject) => {
  //execute your code
  //call resolve if success
  //call reject if failed
})

The function passed to new Promise is called the executor. When new Promise is created, the executor runs automatically.

The promise object returned by the constructor has the following internal properties (We can’t directly access them)

  • state - initially "pending", then changes to "resolved" when success and "rejected" when failed
  • result - initially undefined, then changes to the value when resolve called or the error when reject called.

A promise that is either resolved or rejected is called "settled" as opposed to an initially "pending" promise.
The executer function should call one resolve or one reject and any further calls for resolve or reject will be ignored.

let promise = new Promise<User[]>((resolve, reject) => {
  //execute your code
  resolve(['data1', 'data2']);
  resolve(['']); //ignored
  reject(Error('Error')) //ignored
})

Promise Consumers

  • then => has two arguments the first for success and the second for reject
  • catch => called when an error occurred
  • finally => runs when the promise is settled (success or failed) don't know whether the promise is successful or not.

let's see an example

class User {
  id: number;
  name: string;
}
function getUsers(): User[] {
  return [{id: 1, name: 'Mina'}, {id: 2, name: 'John'}, {id: 3, name: 'Narouz'}]
}
let promise = new Promise<User[]>((resolve, reject) => {
  setTimeout(()=> {
    let users = getUsers();
    if (users.length > 0) {
      resolve(users);
    } else {
      //it is recommended to use Error object with reject
      reject(Error('Error, No Users found'));
    }
  }, 1000)
})

promise
.then((users: User[]) => {
  users.forEach(user=> {
    console.log(user.name);
  })
})
.catch((err: Error) => {
  console.log(err.message);
})
.finally(() => {
  console.log('finished');
})

Promise.all

Is a method returns a single Promise that fulfills when all of the promises passed as an iterable have been fulfilled

in some cases, you need to do execute behavior after multiple promises have done so instead of doing nested callbacks the better to use Promise.all as in the following example

let p1 = Promise.resolve(22);
let p2 = Promise.resolve('Data returned');
Promise.all([p1, p2]).then((res: [number, string]) => {
  let p1Res = res[0]; //22
  let p2Res = res[0]; //Data returned
})

but If any of the passed-in promises reject, Promise.all asynchronously rejects with the value of the promise that rejected, whether or not the other promises have resolved.

let p3 = Promise.all([1,2,3, Promise.reject(555)]);
// Promise { <state>: "rejected", <reason>: 555 }

It is possible to change this behavior by handling possible rejections:

var p1 = new Promise((resolve, reject) => { 
  setTimeout(() => resolve('p1 success result'), 1000); 
}); 

var p2 = new Promise((resolve, reject) => {
  reject(new Error('p2 Error'));
});

Promise.all([
  p1.catch(error => { return error }),
  p2.catch(error => { return error }),
]).then((values: [string, string]) => { 
  console.log(values[0]) // "p1 success result"
  console.error(values[1]) // "p2 Error"
})

3. Async/Await

Async functions make it possible to treat functions returning promises as if they were synchronous. It can be used in place of or with promises.

async function func(): Promise<string> {

  let promise = new Promise<string>((resolve, reject) => {
    setTimeout(() => resolve("Got data"), 1000)
  });

  let result = await promise; // wait until the promise resolves

  return result; // "Got data"
}

func().then(res => console.log(res));

async before function means functions always returns a promise. await only works inside async functions and makes JavaScript wait until that promise settles and returns its result.

Error Handling

It is recommended to use try/catch/finally with async/await

async function getData() {
  try {
    let promise = new Promise<string>((resolve, reject) => {
      setTimeout(() => reject(Error('Errors!')), 1000)
    });
    const res = await promise;
  } catch (error) {
    console.log(error.message);
  } finally {
    console.log('done');
  }
}

Promise.all with async/await

You can use Promise.all with async/await functions as the following

async function getAllDataForUser(userId: number) {
  let response = await fetch(`/api/users/${userId}`);
  let user = await response.json();
  const booksResponse = await fetch(`/api/users/${userId}/books`);
  const ordersResponse = await fetch(`/api/users/${userId}/orders`);
  const [books, orders] = await Promise.all([
    booksResponse.json(),
    ordersResponse.json(),
  ]);
  user.books = books;
  user.orders = orders;
  return user;
}

for await of

If you need to Make one async call at a time you can use for await of instead of Promise.all as in the following example I will replace Promise.all in the previous example

async function getAllDataForUser(userId: number) {
  let response = await fetch(`/api/users/${userId}`);
  let user = await response.json();
  const booksResponse = await fetch(`/api/users/${userId}/books`);
  const ordersResponse = await fetch(`/api/users/${userId}/orders`);
  let asyncCalls = [booksResponse.json(), ordersResponse.json()];
  let index = 0;
  for await (let res of asyncCalls) {
    if (index == 0) {
      user.books = res;
    } else {
      user.orders = res;
    }
    index++;
  }
  return user;
}

Posted on by:

minamarouftawfik profile

Mina Marouf

@minamarouftawfik

I am a principal software developer and have a deep experience in front-end proramming specially in angular framework

Discussion

pic
Editor guide