JS : Pemrograman Asinkron

Mayoritas kode JavaScript yang pernah kita tulis bersifat synchronous, artinya setiap baris kode dieksekusi secara berurutan setelah baris sebelumnya selesai. Tapi nggak jarang juga kita nulis kode yang bersifat asynchronous, yaitu function yang nggak langsung selesai & selama function ini belum selesai, JavaScript engine bisa jalanin kode kita yang lain. Salah satu contoh kode asinkron yang paling sering kita tulis adalah network request, baik pake XMLHTTPRequest atau, kalo pake JQuery, ya $.ajax().

Contoh paling sederhananya begini,

console.log(1);
console.log(2);
console.log(3);
console.log(4);
console.log(5);

Kode di atas semuanya bersifat sinkron, satu baris dijalanin sesudah baris sebelumnya selesai. Outputnya begini,

1
2
3
4
5

Kode yang asinkron contohnya seperti di bawah ini,

console.log(1);
console.log(2);

setTimeout(()=>{
  console.log(3, 'asinkron'); // asinkron
},500);

console.log(4);
console.log(5);

Outputnya begini, keliatan bedanya kan? Kode yang asinkron baru selesai setelah baris terakhir dijalanin.

1
2
4
5
3 'asinkron'

Callback

Cara penulisan kode asinkron yang paling sederhana adalah pake callback. Seperti contoh setTimeout di atas. Contoh callback yang lain bisa kita tulis juga dalam bentuk event-listener misalnya document.addEventListener("DOMContentLoaded", listenerFn).

Promise

ES6 memperkenalkan cara “baru” untuk menulis kode asinkron, pake objek yang namanya Promise. Kelebihan Promise dibanding callback biasa salah satunya adalah adanya mekanisme untuk menghandel error, kondisi dimana proses gagal & nggak bisa selesai. Promise tetap pake callback tapi dengan cara yang lebih elegan dan terstandarisasi. Sintaks dasarnya seperti di bawah,

const promise = new Promise((resolve,reject)=>{
    // kalo proses berhasil, panggil resolve()
    // kalo proses gagal, panggil reject()
});

Kode yang menghandel hasilnya harus kasih callback lewat then() dan catch(). Kalo Promise berhasil, yang dijalanin adalah callback yang dikirim lewat then(), sebaliknya kalo Promise gagal, yang dijalanin adalah callback yang dikirim lewat catch()

promise
.then(()=>{}) // promise berhasil (resolve)
.catch(()=>{}) // promise gagal (reject)

Untuk contohnya, coba tulis kode berikut & biar ga usah repot buka browser jalanin pake node aja. Jadi silakan bikin skrip JS, terus di terminal jalanin node namaskrip.js.

const promise = new Promise((resolve,reject)=>{
    setTimeout(()=>{
      const num = Math.round(Math.random() * 100);
      if(num % 2 === 0){
        resolve(num);
      }else{
        reject(num);
      }
    }, 100);
})

promise
  .then((num)=>{
    console.log('ok', num);
  }).catch((num)=>{
    console.error('not ok', num);
  });

Dalam contoh kode di atas, Promise kita anggap sukses (resolve) kalo konstanta num bernilai genap setelah 0,1 detik. Karena kita pake Math.random(), Promise nggak selalu resolve, kadang reject juga.

Library JS yang secara internal ngejalanin proses asinkron semuanya mengirim objek Promise sebagai nilai baliknya. Salah satu contohnya fetch. Kita nggak perlu bikin Promise sendiri tapi tinggal pasang callback di then() & catch().

Satu Promise bisa dikasih lebih dari satu then & catch.

const promise = new Promise((resolve,reject)=>{
    setTimeout(()=>{
      const num = Math.round(Math.random() * 100);
      if(num % 2 === 0){
        resolve(num);
      }else{
        reject(num);
      }
    }, 100);
})

// then..catch pertama
promise
  .then((num)=>{
    console.log('ok', num);
  }).catch((num)=>{
    console.error('not ok', num);
  });
  
// then..catch kedua
promise
  .then( num=>{
    console.log('ok ok',num)
  }).catch( num => { console.error('not ok ok', num)} );

Promise Chain

Salah satu kelebihan Promise dibanding skema callback biasa adalah kemudahan dalam membentuk rantai proses asinkron. Kita pasti sering ketemu kasus di mana kita harus jalanin beberapa proses secara berurutan, satu proses setelah proses sebelumnya selesai. Kalo pake callback biasa, kita pasti terpaksa nulis kode yang kadang disebut callback-hell seperti di bawah,

const proses1 = (callback, err)=>{
  setTimeout(()=>{
    callback('proses1 selesai');
  }, 1000);
}

const proses2 = (callback, err)=>{
  setTimeout(()=>{
    callback('proses2 selesai');
  }, 1000);
}

const proses3 = (callback, )=>{
  setTimeout(()=>{
    callback('proses3 selesai');
  }, 1000);
}

proses1((msg1)=>{
  console.log(msg1);
  proses2((msg2)=>{
    console.log(msg2);
    proses3((msg3)=>{
      console.log(msg3);
    })
  })
})

Baru tiga proses aja kodenya udah ruwet. Apalagi prosesnya lebih banyak & ada callback buat errornya juga. Kalo pake Promise, kodenya bisa jadi lebih bersih & gampang dibaca. Kita tinggal me-return Promise di setiap then() secara berantai.

const proses1 = ()=>{
  return new Promise((resolve,reject)=>{
    setTimeout(()=>{
      resolve('proses1 selesai');
    }, 1000);
  })
}

const proses2 = ()=>{
  return new Promise((resolve,reject)=>{
    setTimeout(()=>{
      resolve('proses2 selesai');
    }, 1000);
  })
}

const proses3 = ()=>{
  return new Promise((resolve,reject)=>{
    setTimeout(()=>{
      resolve('proses3 selesai');
    }, 1000);
  })
}

proses1()
  .then((msg)=>{
    console.log(msg);
    return proses2();
  })  
  .then((msg)=>{
    console.log(msg);
    return proses3();
  })
  .then((msg)=>{
    console.log(msg);
  })
  .catch((err)=>{
    console.log('ERROR:', err);
  })

Outputnya,

proses1 selesai
proses2 selesai
proses3 selesai

Menghandel error juga lebih gampang karena kalo di tengah-tengah chain ada error, proses otomatis berhenti. Jadi dalam contoh kode di atas, kalo proses2 nggak resolve, proses3 nggak akan jalan.

const proses1 = ()=>{
  return new Promise((resolve,reject)=>{
    setTimeout(()=>{
      resolve('proses1 selesai');
    }, 1000);
  })
}

const proses2 = ()=>{
  return new Promise((resolve,reject)=>{
    setTimeout(()=>{
      // ERROR DI SINI
      reject('proses2 error');
    }, 1000);
  })
}

const proses3 = ()=>{
  return new Promise((resolve,reject)=>{
    setTimeout(()=>{
      resolve('proses3 selesai');
    }, 1000);
  })
}

proses1()
  .then((msg)=>{
    console.log(msg);
    return proses2();
  })  
  .then((msg)=>{
    console.log(msg);
    return proses3();
  })
  .then((msg)=>{
    console.log(msg);
  })
  .catch((err)=>{
    console.log('ERROR:',err);
  })

Outputnya:

proses1 selesai
ERROR: proses2 error

Parallel Promise

Promise nggak harus berurutan (chain) seperti di atas. Bisa juga dijalanin secara paralel. Kita tinggal pake Promise.all().then() untuk jalanin proses setelah semua Promise selesai.

const proses1 = ()=>{
  return new Promise((resolve,reject)=>{
    setTimeout(()=>{
      resolve('proses1 selesai');
    }, 1000);
  })
}

const proses2 = ()=>{
  return new Promise((resolve,reject)=>{
    setTimeout(()=>{
      resolve('proses2 selesai');
    }, 1000);
  })
}

const proses3 = ()=>{
  return new Promise((resolve,reject)=>{
    setTimeout(()=>{
      resolve('proses3 selesai');
    }, 1000);
  })
}

const proses4 = Promise.all([proses1(),proses2(),proses3()]);
proses4.then((messages)=>{
  console.log(messages)
}).catch(err=>{
  // kalo ada promise yang error
  console.log('ERROR:', err);
})
[ 'proses1 selesai', 'proses2 selesai', 'proses3 selesai' ]

Coba bikin kode yang sama seperti di atas tapi pake callback biasa, pasti lebih rumit.

Promise Race

Promise.all() bukan satu-satunya API yang bisa kita pake. Ada variasinya yaitu Promise.race(). Bedanya kalo Promise.all() nunggu semua Promise selesai, Promise.race() hanya perlu satu Promise selesai.

const proses1 = ()=>{
  return new Promise((resolve,reject)=>{
    setTimeout(()=>{
      resolve('proses1 selesai');
    }, 3000);
  })
}

const proses2 = ()=>{
  return new Promise((resolve,reject)=>{
    setTimeout(()=>{
      resolve('proses2 selesai');
    }, 1000);
  })
}

const proses3 = ()=>{
  return new Promise((resolve,reject)=>{
    setTimeout(()=>{
      resolve('proses3 selesai');
    }, 500);
  })
}

const proses4 = Promise.race([proses1(),proses2(),proses3()]);
proses4.then((messages)=>{
  console.log(messages)
}).catch(err=>{
  console.log(err);
})

Karena proses3 selesai duluan, proses1 & proses2 nggak penting lagi. Jadi outputnya:

proses3 selesai

O iya, Promise nggak di-support oleh Internet Explorer. Jadi kalo aplikasi Anda harus jalan di IE, silakan pake polyfill misalnya promise-polyfill atau es6-promise.

Oke. Sekian artikelnya. Mudah-mudahan bermanfaat.

Also in this category ...


Leave a Reply

Your email address will not be published. Required fields are marked *