Tìm hiểu về $q và Promise trong Angular

$q và Promise trong Angular

Chắc hẳn mọi người đều đã từng nhìn thấy hoặc đã từng làm việc với $q khi sử dụng angular, chăng bạn có chắc đã nắm được hết những tính năng tuyệt với của nó mang lại như là $q.all() , $q.race(). Bài viết này mình sẽ nói về một vài điều hay ho về nó.

Promise là cái quái gì ?

Promise là một type đặc biệt của 1 Object có thể sử dụng hay để cấu trúc việc xử lý bất đồng bộ. Tại sao lại gọi nó là "Promise" có lẽ cũng giống như nghĩa của nó "hẹn ước" 😃) nếu A hoàn thành thì B thực hiện, nó sẽ trả về 1 kết quả ở 1 thời điểm nào đó trong tương lai. Một Promise có 3 trạng thái pending, resolvedrejected. Xử dụng $q trong Angular chúng ta có thể xấy dựng và xử lý được các promises của nó. (có lẽ nói đến đấy thấy hơi khó hiểu vì mình cũng ko hiểu mình đang nói cái ... gì 😃)) Để làm rõ cái "hồ mơ" trên thì chúng ta sẽ bắt đầu với 1 ví dụ làm quen với Promise trong ES2015.

ES5 Promises

Những cái chính đề cập tới ở đây sẽ là Promise, resolve, và reject

new Promise(/* executor*/ function (resolve, reject) { ... } );

chúng ta chỉ cần khai báo new Promise() vào xử lý các tác vụ bất đồng bộ bên trong. Ví dụ

/* ES5 */
var isHaveMoney = false;

// Promise
var willDrinkBeer = new Promise(
    function (resolve, reject) {
        if (isHaveMoney) {
            var happy =  "Happy"
            resolve(happy); 
        } else {
            var reason = new Error('So bad');
            reject(reason); 
        }

    }
);

// call our promise
var checkMoney = function () {
    willDrinkBeer
        .then(function (fulfilled) {
            // yeah chúng ta có tiền và có bia
            console.log(fulfilled);
        })
        .catch(function (error) {
            // ví hết tiền, chịu thôi !
            console.log(error.message);
        });
}

checkMoney(); // kết quả chắc chắn là không có tiền rồi vì ông trời set default ở trên là isHaveMoney = false; rồi :(((

Giới thiệu $q

$q là một thành phần của AngularJS nó giống như Promise trong ES5. vì thế chúng ta có thể thực hiện như sau:

let promise = $q((resolve, reject) => {
  if (/* some async task is all good */) {
    resolve('Success!');
  } else {
    reject('Oops... something went wrong');
  }
});

promise.then(data => {
  console.log(data);
});

Khác thay vì new Promise() chúng ta chỉ cần $q() và cách thực hiện của nó trong 1 service ở angular như sau :

function MyService($q) {
  return {
    getSomething() {
      return $q((resolve, reject) => {
        if (/* some async task is all good */) {
          resolve('Success!');
        } else {
          reject('Oops... something went wrong');
        }
      });
    }
  };
}

angular
  .module('app')
  .service('MyService', MyService);

Chúng ta có thể tạo ra 1 đối tượng Promise như thế này :

function getStuff() {
  return $http
    .get('/api/stuff');
    .then(data => {
      console.log('Boom!', data);
    });
}

getStuff().then(data => {
  console.log('Boom!', data);
});

chúng ta không nên tạo ra 1 đối tượng Promise như thế này vì nó sẽ tạo ra một đối tương Promise mới từ Promise đã tồn tại :

function getStuff() {
  // don't do this!
  let defer = $q.defer();
  $http
    .get('/api/stuff');
    .then(response => {
      // don't do this!
      $q.resolve(response);
    }, reason => {
      // don't do this!
      $q.reject(reason);
    });
  return defer.promise;
}

getStuff().then(data => {
  console.log('Boom!', data);
});

$q.defer()

Sử dụng $q.defer() như một thành phần nguyên thủy của $q, giống như cấu trúc của một Promise

function MyService($q) {
  return {
    getSomething() {
      let defer = $q.defer();
      if (/* some async task is all good */) {
        defer.resolve('Success!');
      } else {
        defer.reject('Oops... something went wrong');
      }
      return defer.promise;
    }
  };
}

$q.when() / $q.resolve()

Sử dụng $q.when() hoặc $q.resolve() ($q.resolve() là một alias của $q.when()) khi chúng ta muốn resolve một Promise từ Object không phải Promise.

$q.when(123).then(res => {
  // 123
  console.log(res);
});

$q.resolve(123).then(res => {
  // 123
  console.log(res);
});

$q.reject()

Sử dụng $q.reject() khi bạn muốn reject một Promise

$httpProvider.interceptors.push($q => ({
  request(config) {...},
  requestError(config) {
    return $q.reject(config);
  },
  response(response) {...},
  responseError(response) {
    return $q.reject(response);
  }
}));

$q.all()

Khi bạn cần resolve nhiều promise một lần bằng cách truyền vào 1 array các promise, cái này sẽ thực hiện .when() sau khi resolved

let promiseOne = $http.get('/api/todos');
let promiseTwo = $http.get('/api/comments');

// Array of Promises
$q.all([promiseOne, promiseTwo]).then(data => {
  console.log('Both promises have resolved', data);
});

// Object hash of Promises
// this is ES2015 shorthand for { promiseOne: promiseOne, promiseTwo: promiseTwo }
$q.all({
    promiseOne,
    promiseTwo
  }).then(data => {
  console.log('Both promises have resolved', data);
});

$q.race()

Tương tự như $q.all() phương thức $q.race() chỉ trả về 1 promise được resolve đầu tiên, ví dụ API 1 và API 2 cùng thực hiện, API 2 được resolve trước thì nó chỉ lấy Object được trả về từ API 2.


let promiseOne = $http.get('/api/todos');
let promiseTwo = $http.get('/api/comments');

// Array of Promises
$q.race([promiseOne, promiseTwo]).then(data => {
  console.log('Fastest wins, who will it be?...', data);
});

// Object hash of Promises
// this is ES2015 shorthand for { promiseOne: promiseOne, promiseTwo: promiseTwo }
$q.race({
    promiseOne,
    promiseTwo
  }).then(data => {
  console.log('Fastest wins, who will it be?...', data);
});

Kết luận

Bằng việc sử Promise và $q chúng ta có thể xử lý đựọc bất đồng bộ trong AngularJS, và sử dụng $q.all() hoặc $q.race() để xử lý các promise đã có. Đây là những tìm hiểu của mình về Promise trong ES5 cũng như $q trong AngularJS nhằm giải quyết các vấn đề bất đồng bộ. hy vọng sẽ có ích cho mọi người. Nếu có gì sai xót mọi người góp ý!.

Link tham khảo : https://docs.angularjs.org/api/ng/service/$q