Vietnam

    Nodejs.vn

    • Register
    • Login
    • Search
    • Categories
    • Recent
    • Popular
    • Tags
    • Groups
    • Search

    Promise – Async thì sao nào?

    Frontend
    javascript
    6
    16
    20740
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • Lê Mạnh Hùng
      Lê Mạnh Hùng last edited by

      Quiz nhỏ: 😊

      Delay 5s rồi log ra console chữ HELLO WORLD ??

      setTimeout(function() {
          console.log('HELLO WORLD');
      }, 5000);
      

      Nhẹ nhàng hơn cả đẩy xe hàng, ok?

      Quiz nhỡ:

      Delay 5s rồi log ra console chữ HELLO
      Rồi 3s sau đó log ra chữ WORLD
      Rồi 1s sau đó log ra chữ Rikky Handsome?

      setTimeout(function() {
          console.log('HELLO');
          setTimeout(function() {
              console.log("WORLD");
              setTimeout(function(){
                  console.log('Rikky Handsome');
              }, 1000)
          }, 3000)
      }, 5000);
      

      Quiz to: LÀM THẾ NÀO VIẾT ASYNC CHO ĐẸP ?

      Async là một tác vụ của máy tính mà nó sẽ được hoàn thành trong TƯƠNG LAI GẦN. Async có thể giản lược về 3 trạng thái cơ bản: PENDING, SUCCESS, FAILED.

      Khi một async task bắt đầu được thực hiện, nó PHẢI ở trạng thái PENDING.
      Sau khi thực hiện xong task, async PHẢI chuyển về 1 trong 2 trạng thái: SUCCESS hoặc là FAILED.
      Sau khi đã chuyển về trạng thái SUCCESS hoặc là FAILED, async đó KHÔNG ĐƯỢC PHÉP thay đổi trạng thái nữa.

      Async KHÁC Event, nhưng có thể dùng event để phát sự kiện chuyển trạng thái của async. Khi đó, event chuyển trạng thái chỉ được trigger 1 lần.

      Đây là bộ nguyên tắc bất di bất dịch mà theo như HIỂU BIẾT của mình về async. OK không?

      OKAY, BÂY GIỜ QUAY LẠI VẤN ĐỀ ASYNC XẤU XÍ CỦA JAVASCRIPT

      Cách Javascript xử lý các tác vụ async có một vấn đề: Sự ràng buộc giữa task handler và task. Ờ thì nôm na ra là:

      setTimeout Là task

      function() { console.log('Hello World'); } là task handler.

      Hai cái này vẫn đang phải viết chung vào 1 chỗ:

      setTimeout(function() {
          console.log('Hello World');
      }, 5000);
      

      Vấn đề sẽ ngay lập tức bị phát sinh, nếu bản thân task handler lại là 1 task khác:

      setTimeout(function() {
          console.log('HELLO');
          setTimeout(function() {
              console.log("WORLD");
              setTimeout(function(){
                  console.log('Rikky Handsome');
              }, 1000)
          }, 3000)
      }, 5000);
      

      Code, nhìn từ góc độ thẩm mỹ thôi nhé, đã xấu bỏ xừ. Các callback function lồng nhau được gọi là hiện tượng: Callback Hell hay Pyramid of Doom.

      Nhìn từ góc độ thiết kế, cách viết này sẽ có nguy cơ dẫn đến sự vi phạm những design-pattern căn bản.
      Cụ thể nhé:
      Nếu mình muốn viết một đoạn mã lấy dữ liệu từ db và trả ra http response:

      exports.showData = function(request, response) {
          db.getData(function(data) {
              response.json(data);
          });
      }
      

      Điểm mấu chốt nằm ở chỗ, db là object làm việc với database, response là object của http-server. Nhưng bây giờ, db đã phải phụ thuộc hoàn toàn vào response. Điều này vi phạm tư tưởng single response trong thiết kế hệ thống.

      Chưa kể đến, rất có thể db là một thư viện do một nhóm deverloper phát triển độc lập với nhóm xây dựng http server. Để nhóm xây dựng http-server sử dụng được db cần có tài liệu mô tả về hàmgetData xem callback function sau đó sẽ nhận được những parameter nào, thậm chí là structure của chúng. Điều này làm giảm đáng kể tốc độ phát triển dự án.

      ASYNC INTERFACE – PROMISE

      PROMISE – chính xác hơn là Promise/A+ là một bản đặc tả về kết quả của một tác vụ ASYNC, nó đã trở thành một tiêu chuẩn, một INTERFACE để xây dựng những tác vụ Async. Nó giúp bóc tách async task và task handler ra khỏi nhau, những async task như vậy được gọi là những deferred, và kết quả của deferred là promise.

      Sau đây là một số đặc tả quan trọng:

      Một thenable là một Javascript object có chứa method then(), method then() này nhận 2 tham số fullfill và reject theo đúng thứ tự. Cả 2 tham số này đều là những callback function.

      Ví dụ:

      aThenable.then(function(value){
           // onFullfill
      }, function(error){
          // onReject
      })
      

      Một promise là một thenable thoả mãn:

      Có chỉ có thể có 1 trong 3 trạng thái sau tại một thời điểm: pending, fullfill, reject

      NGAY tại thời điểm được khởi tạo, promise phải mang trạng thái pending

      Khi async task được thực hiện thành công, promise phải chuyển từ trạng thái pending sang trạng thái fullfill, tại thời điểm đó, callback onFullfill phải được thực hiện, onFullfillchỉ nhận 1 và chỉ một tham số, đại diện cho kết quả của async task.

      Khi async task được THỰC HIỆN XONG, NHƯNG KHÔNG THÀNH CÔNG, promise phải chuyển từ trạng thái pending sang trạng thái reject, tại thời điểm đó, callback onReject phải được thực hiện, onReject chỉ nhận 1 và chỉ 1 tham số, đại diện cho thông báo lỗi do async task trả về.

      Khi promise đã ở trại thái fullfill hoặc reject, promise đó KHÔNG thay đổi trạng thái.

      Khi một promise có onFullfill hoặc onReject trả về một thenable, thì hàm then của promise đó sẽ trả ra một promise mới – (Promise Chaining)

      Khi một promise có onFullfill hoặc onReject trả về một value không phải thenable, thì hàm then của promise đó sẽ trả ra một promise mới mà nó sẽ có luôn trạng tháifullfill với value chính là value không phải thenable – (Promise Pipelining)

      Chú ý:

      Hàm then() có thể gọi được nhiều lần.

      aPromise.then(onFullfill1, onReject1);
      aPromise.then(onFullfill2, onReject2);
      ...
      aPromise.then(onFullfillN, onRejectN);
      

      Khi đó, nếu aPromise chuyển về fullfill hoặc reject, TẤT CẢ các callback tương ứng đều được thực hiện;

      Promise có thể chaining (Liên hoàn) – Chú ý 2 cái đặc tả cuối cùng:

      chainingPromise.then(function() {
          /// asyncTask fullfill
          /// start running a new async task
          return aNewPromise
      }, function() {
          ///
      })
      
      .then(function () {
          // The new async task done.
      }, function() {
          ///
      });
      

      Promise KHÔNG PHẢI là async task, mà là đại diện của KẾT QUẢ khi async task được thực hiện xong. Nó nên được hiểu là Value, chứ không phải Action

      var promise = asyncTask(); //  promise  -carries result; asyncTask() -performs task
      
      // Get the value that the promise is carrying
      promise.then(function(result) {
           console.log('Oh! The result is: %', result.toString());
      });
      

      Phù phù! Nhức đầu chưa??? Đấy mới chỉ là những đặc tả cơ bản :))))
      Cơ mà, chỉ cần bạn giữ 1 tư tưởng duy nhất trong đầu:

      Promise là kết quả của Async

      Như thế là ok thôi.

      Nào, thử quay lại cái Quiz nhỡ:

      deferredTimeout(500)
      
      .then(function(){
          console.log('Hello');
      })
      
      .then(function() {
          return deferredTimeout(300);
      })
      
      .then(function() {
          console.log('World');
      })
      
      .then(function() {
          return deferredTimeout(100);
      })
      
      
      .then(function() {
          console.log('Rikky Handsome');
      });
      

      Xinh chưa ☺

      Vâng, đây chính là một ví dụ về ASYNC WATERFALL hay Consequence Async ạ

      PROMISE IMPLEMENTATIONS

      Bạn sẽ hỏi:

      Mày lôi ở đâu ra hàm deferredTimeout() thế?
      Trả lời luôn: Bịa ra đấy, không có đâu =)))

      Bạn nên chú ý, Promise/A+ chỉ là đặc tả, nó mô tả một mô hình, một khuôn mẫu. Còn việc implement nó? Đã có rất nhiều thư viện :D. Và bây giờ là một vài gương mặt sáng giá.

      JQUERY

      Từ bản 1.7.0, jQuery đã implement mô hình promise.

      Không tin hả? Thử cái này xem:

      $.get('/my/url').then(function(data) {
           console.log(data);
      });
      

      Công việc async duy nhất của jQuery phải làm chính là ajax. Và toàn bộ ajax của jQuery 1.7 đã được viết lại theo cách này.

      Ngoài ra, jQuery cũng hỗ trợ bạn tự xây dựng ra các promise của riêng mình thông qua object jQuery.Deferred() – check it out: http://api.jquery.com/category/deferred-object/

      Ví dụ nhé:

      var wait = function(milisec) {
           var deferred = $.Deferred();
           setTimeout(deferred.resolve, milisec);
           return deferred.promise();
      };
      
      wait(500).then(function() {
           console.log('Rikky is F**king awesome!');
      });
      

      Nhá, deferredTimeout() đây nhá.

      Q

      Q là một thư viện có thể dùng cho cả Client/Node.JS;
      Q được nhiều người biết đến vì nó chính là 1 built-in service của AngularJS ($q);
      Q implement rất tốt spec của Promise/A+
      Q hỗ trợ xây dựng một deferred khá dễ dàng:

      var wait = function(milisec){
          var defer = Q.defer();
          setTimeout(defer.resolve, milisec);
          return defer.promise;
      };
      

      Ngoài ra, ta còn có when cũng rất đáng chú ý.
      Parse.com – một PaaS và IaaS rất nổi tiếng hiện nay, SDK của nó cũng support Promise để viết JavascriptClient và CloudCode.
      Với ES6 spec, tương lai, promise sẽ được native support cho javascript. Cool!

      Q? Vậy Async library của NodeJS là gì?
      A: Chỉ là một cách tiếp cận khác đến xử lý async task. Được phát triển bởi developer tên là Caolan. Nó không phải là tiêu chuẩn, càng không phải là Cách lập trình Async. Tuy vậy, Caolan là một developer xuất sắc, anh cũng chính là cha đẻ của nodeunit. Mình tôn trọng thư viện và cách tiếp cận vấn đề của anh. Nhưng mình sẽ chỉ quan tâm đến Promise, vì nó là cách tiếp cận chính thống, được cộng đồng công nhận từ lâu.

      ỨNG DỤNG PROMISE TRONG THỰC TẾ

      Promise như là contract cho các tác vụ async:

      Design by Contract là một phương pháp phát triển phần mềm theo nguyên tắc: Sử dụng một hệ thống Interface/Đặc tả để định nghĩa (Bằng Lập Trình) các điểm ghép nối giữa các thành phần của phần mềm. Nó được xây dựng khi coi một thành phần sẽ là:

      Nhà cung cấp, sẽ cung cấp thư viện, các API để có thể thực hiện một công việc nào đó nếu như nó được chia sẻ đủ thông tin. Và nó CAM KẾT thực hiện công việc đó.

      Một thành phần khác là Khách hàng sử dụng thư viện và API của Nhà cung cấp để thực hiện công việc của mình. Khách hàng đồng thời CAM KẾT việc chia sẻ đủ thông tin cho Nhà cung cấp.

      Contract là sự cam kết giữa Nhà cung cấp và Khách Hàng.

      Thông thường, Khách hàng và Nhà cung cấp không cần biết đối tác của mình là ai, chỉ cần biết đối tác sẽ thực hiện cam kết của mình, nhờ thế, Nhà cung cấp và Khách hàng có thể được xây dựng độc lập, không phụ thuộc lẫn nhau.

      RẤT LÀ AGILE =))

      Trong quá trình phát triển dự án bằng Javascript, rất nhiều trường hợp, bên Nhà cung cấp là sẽ phải thực hiện một công việc async. Khi đó, ta thường sử dụng promise như là contract.

      Ví dụ:
      Bạn đang viết một module xử lý Authenticate, khi đó, code của controller/middleware của bạn chính là Khách Hàng. Nó sử dụng Nhà Cung Cấp là một dịch vụ Authenticator thông quausername và password và trả lại một userIdentity nào đó:

      module.exports = function(request, response) {
          ///
         Authenticator.check(request.body.username, response.body.password);
         /// 
      }
      

      Vấn đề là việc check này, trong hầu hết các trường hợp là tác vụ async (như query đến database chẳng hạn); Nên ta cần có 1 promise để define contract – Authenticator.check(username, password) Phải return 1 promise đại diện cho kết quả login.
      Và thế là ta đã có đoạn code sau:

      module.exports = function(request, response) {
         ///
         Authenticator.check(request.body.username, response.body.password)
         // Interesting things here:
         .then(function(userIdentity) {
             response.send('Hello ' + userIdentity); // Kiểu kiểu thế
         }, function(authErrorMessage) {
            response.status(401).send(authErrorMessage);
         });
         /// 
      }
      

      Okay, thế có gì hot?
      HOT là bạn thực sự không cần biết Authenticator thực chất là object nào.

      var InnerSystemAuthenticator = function () {
           /// 
           this.check = function(u, p) {
                 var loginDefer = Q.defer;
                 db.find({username: u, password: p}, function(error, found) {
                     if(error) {
                          defer.reject(error);
                     }
                     if (!found) {
                          defer.reject('Authentication Failed');
                     } else {
                          defer.resolve(found.id);
                     }
                 });
                 return loginDefer.promise;
           };
      };
      
      var FacebookAuthenticator = function() {
            this.check = function (u, p) {
                  // ... blah
                  return fbAuthPromise;
            };
      };
      
      var GoogleAuthenticator = function() {
          this.check = function (u, p) {
                  // ... blah
                  return googleAuthPromise;
            };
      };
      

      Và bây giờ, với chỉ một promise contract đơn giản, bạn đã có thể cùng một lúc tích hợp cả 3 service Authenticate: DB, Facebook, Google.

      Và 100 năm nữa, khi tập đoàn Rikky phát triển dịch vụ xác thực thông qua username là DNA và password là vân tay của user. Bạn vẫn tích hợp được nó vào dự án của bạn. Cheer!

      Promise như là tầng abstract của async

      Quay lại vụ Authenticate, nếu bạn đang chỉ muốn viết unittest cho controller/middleware xem Nếu user authenticate failed có đúng http status 401 được bắn ra hay không? bạn làm như nào:

      var AlwaysFailedMockAuthenticator = {
          check: function(u, p) {
               var failedAuthDefer = Q.defer();
               failedAuthDefer.reject('Just Kidding! Test for fun!');
               return failedAuthDefer.promise;
          }
      };
      

      **RẤT LÀ TDD 😚 **

      Promise như là KẾT QUẢ của async

      Nếu 1 ngày đẹp trời, bạn phát hiện ra, việc gì mình phải thực hiện nhiều lần query đến cái data dở hơi, 100 năm mới thay đổi 1 lần.

          db.findCurrentCentury().then( // blah blah);
      

      Thì hãy làm như sau:

      var CurrentCenturyProvider = function() {
          //
          var cached = null;
      
          this.get = function() {
              if (!cached) {
                  return db.findCurrentCentury().then(function(currentCentury) {
                      cached = currentCentury;
                      return currentCentury;
                  });
              } else {
                  var cachedPromise = Q.defer();
                  q.resolve(cached);
                  return q.promise;
              }
          };
      }
      
      
      var centuryProvider = new CurrentCenturyProvider();
      
      centuryProvider.get().then( // blah blah);
      

      RẤT LÀ PERFORMANCE 😊

      Promise như là kết quả của một cái gì đó – có thể là Async

      Một ngày đẹp trời, bạn sẽ nhận ra rằng Sync cũng là Async, một trường hợp đặc biệt của Async thì đúng hơn. Nếu thế, hãy cứ coi như task của bạn sẽ chấp nhận 1 promise đi. Nó rất hữu ích khi bạn xây dựng 1 thư viện mà bạn không biết chính xác input của bạn có phải Async hay không.

      // Nào thì jQuery 1 tý nào:
      var inputTextAutoComplete = function (listOfSuggestion) {
           // Wrap luôn listOfSuggestion bởi 1 promise:
           var suggestionPromise = jQuery.when(listOfSuggestion);
      
           suggestionPromise.then(function() {
               /// Show the AutoComplete
           });
      };
      
      inputTextAutoComplete(['Rikky', 'Is', 'Awesome']);
      inputTextAutoComplete($.get('/keywords'));
      

      RẤT LÀ TINH TẾ =)))))))

      Promise thay thế cho những event chỉ được trigger 1 lần:

      Như mình đã nói, promise và những event chỉ được trigger 1 lần có thể hoán đổi cho nhau, ví dụ như onload/ready event của jQuery:

      var whenLoaded = function() {
          var deferred = $.Deferred();
          $(document).ready(function(event) {
              deferred.resolve(event);
          });
          return deferred.promise();
      };
      ///
      var pageLoadedPromise = whenLoaded();
      pageLoadedPromise.then(function(event) {
          console.log('Woo hoo! Page Loaded');
      });
      
      pageLoadedPromise.then(function(){
          // whatever!
      });
      

      Hay thậm chí bạn có thể viết lazy load cho Javascript:

      var lazyLoadJavascript = function(uri) {
             var deferred = $.Deffered();
             $.getScript(uri, function() {
                  deferred.resolve();
            });
            return deferred.promise();
      };
      
      var myScriptPromise = lazyLoadJavascript('path/to/my/script.js');
      myScriptPromise.then(function() {
           console.log('Woo hoo! Script loaded');
      });
      

      hay thậm chí là:

      var myModulePromise = loadModule('path/to/file.html', 'path/to/file.js', 'path/to/file.css');
      
      myModule.then(function() {
           /// whatever :D
      });
      

      KẾT

      Có rất nhiều cách để bạn sử dụng promise, nó gúp Async chẳng gì là phức tạp, hết cả callback lồng nhau. Code của bạn sẽ trở nên hay ho và nguy hiểm hơn nhiều.
      Ngoài các cách implement trên, bạn còn có thể dùng nó để điều khiển chuyển động. Kiểu: moveLeft().then(moveUp).then(moveDown).then(moveRight) –> Rất là chóng mặt s-(
      Nào? Bây giờ hiểu biết của mình như thế OK chưa?
      –> Rất là hiểu biết

      http://fantasticvn.com/

      1 Reply Last reply Reply Quote 8
      • T
        thuanitdn last edited by

        Mình code frontend vs backend chỉ dùng mỗi thư viện này để giải quyết vấn đề này
        https://github.com/caolan/async

        🙂

        J 1 Reply Last reply Reply Quote 0
        • K
          kxd993 last edited by

          Thanks bác cuối cùng cũng có người post bài này 🙂 e copy lại cho chắc

          1 Reply Last reply Reply Quote 0
          • J
            jokyspy @thuanitdn last edited by

            @thuanitdn Mình cũng rất thích thư viện này. Nhưng khi dùng async thì không có kiểu promise1.then(promise2).then(promise3) đẹp mắt như thế kia. 😢

            1 Reply Last reply Reply Quote 0
            • T
              truongnguyen last edited by cuuthegioi

              Nếu được thì các anh có thể viết 1 bài viết về https://github.com/caolan/async được không ak.Để so sánh giữa ASYNC và promise ak

              1 Reply Last reply Reply Quote 0
              • lynam
                lynam last edited by

                Còn cách dùng Async vs await keyword thì thế nào bạn ơi, khi mình tìm hiểu về starter kit React
                họ viết toàn bằng những keyword này, mình hiểu ý nghĩa nhưng lại không biết cách sử dụng và thực hành...

                <3 React, Meteor JS...

                1 Reply Last reply Reply Quote 0
                • trungducng
                  trungducng last edited by

                  This post is deleted!
                  ? rikky Lê Mạnh Hùng 3 Replies Last reply Reply Quote -3
                  • ?
                    A Former User @trungducng last edited by A Former User

                    @trungducng mình thấy bài viết khá vui vẻ. Phải hiểu Promise tương đối sâu mới viết được như vậy.

                    Nhân tiện share cái promise-wtf cây nhà lá vườn. Bluebird và Async thì quá ngon rồi. Nhưng hiện nay hầu hết các trình duyệt đã hỗ trợ built-in Promise, ôm mấy cục Bluebird, Async to đùng về browser thì cũng mệt. Nhưng dùng native Promise thì lại không có cái .finally thành ra hơi thiếu thiếu (finally proposal đang còn ở stage 2). Vậy là phải chế ra em này, inherits cái native, bổ sung finally() và series(). Có buff thêm chút polyfill nhẹ nhẹ - không pass A+ test suite - cho trình duyệt cổ đại, nói chung cũng đủ đồ chơi cho anh em nào thích gọn gàng linh hoạt 😃

                    vahaha 1 Reply Last reply Reply Quote 2
                    • vahaha
                      vahaha @Guest last edited by vahaha

                      @ndaidong Like module của bạn, nhưng mình thấy hàm 'then' có thể đảm nhận vai trò của 'finally'. Ví dụ mình thay đoạn của bạn:

                      //However, it's better to have "finally" there:
                        return Article.list(skip, limit).then((result) => {
                          data.entries = result;
                        }).catch((err) => {
                          data.error = err;
                        }).finally(() => {
                          res.render('landing', data);
                        });
                      };
                      

                      Bằng đoạn:

                      // thay finally bằng then.
                        return Article.list(skip, limit).then((result) => {
                          data.entries = result;
                        }).catch((err) => {
                          data.error = err;
                        }).then(() => {
                          res.render('landing', data);
                        });
                      };
                      
                      • . ^
                      ? 1 Reply Last reply Reply Quote 2
                      • ?
                        A Former User @vahaha last edited by

                        @vahaha thông thường thì đúng như bạn nói. Nhiều trường hợp then có thể thay cho finally. Nhưng nhiều trường hợp thì không. Chẳng hạn trong cái catch mình throw ra 1 exception thì then sau đó sẽ không bắt được.

                        vahaha 1 Reply Last reply Reply Quote 1
                        • vahaha
                          vahaha @Guest last edited by

                          @ndaidong Ah, mình hiểu ý tưởng của bạn rồi. Như vậy thì code sẽ gọn hơn.

                          • . ^
                          1 Reply Last reply Reply Quote 0
                          • rikky
                            rikky @trungducng last edited by

                            @trungducng

                            Tác giả gốc của bài viết đây 😂

                            Số là mình viết bài này cách đây tầm 3 năm, từ cái thời 4rum cũ. Mục đích ban đầu mình viết là để chửi sml mấy bài viết khác trong 4rum vì cái tội chém linh tinh về Promise.

                            Vì sau này 4rum đổi DB, các bài viết cũ không còn nữa, mấy anh em repost bài này lại censore hết các đoạn chửi gay cấn, nên thành ra nó lan man 😁

                            Khoa Lê Duy Tung Nguyen215 2 Replies Last reply Reply Quote 6
                            • Khoa Lê Duy
                              Khoa Lê Duy @rikky last edited by

                              @rikky Không liên quan lắm
                              Nhưng không nhầm bạn làm ở sphinx?
                              Chúng ta đã quen nhau 🙂

                              1 Reply Last reply Reply Quote -5
                              • Lê Mạnh Hùng
                                Lê Mạnh Hùng @trungducng last edited by

                                @trungducng mình xin lỗi 😞

                                http://fantasticvn.com/

                                1 Reply Last reply Reply Quote 6
                                • ?
                                  A Former User last edited by A Former User

                                  Chính chủ về rồi. Bác rikky này mới là cao thủ Node.js chân chính. Sau thời Promise thì nay đã đến thời monads, bữa nào rảnh rỗi làm bài thông não anh em tôn thờ chủ nghĩa functional programming đi bác 😃

                                  1 Reply Last reply Reply Quote 2
                                  • Tung Nguyen215
                                    Tung Nguyen215 @rikky last edited by

                                    Bài này viết từ 2 năm trước rồi của rikky, bạn này đổi tài khoản admin cũ của rikky để restore db. Chứ bạn này ko cố tình sao chép lại.

                                    1 Reply Last reply Reply Quote 0
                                    • First post
                                      Last post
                                    $(document).ready(function () { app.coldLoad(); }); }