AngularJS: Di mana harus menggunakan janji?

141

Saya melihat beberapa contoh layanan Login Facebook yang menggunakan janji untuk mengakses FB Graph API.

Contoh # 1 :

this.api = function(item) {
  var deferred = $q.defer();
  if (item) {
    facebook.FB.api('/' + item, function (result) {
      $rootScope.$apply(function () {
        if (angular.isUndefined(result.error)) {
          deferred.resolve(result);
        } else {
          deferred.reject(result.error);
        }
      });
    });
  }
  return deferred.promise;
}

Dan layanan yang digunakan "$scope.$digest() // Manual scope evaluation"saat mendapat respons

Contoh # 2 :

angular.module('HomePageModule', []).factory('facebookConnect', function() {
    return new function() {
        this.askFacebookForAuthentication = function(fail, success) {
            FB.login(function(response) {
                if (response.authResponse) {
                    FB.api('/me', success);
                } else {
                    fail('User cancelled login or did not fully authorize.');
                }
            });
        }
    }
});

function ConnectCtrl(facebookConnect, $scope, $resource) {

    $scope.user = {}
    $scope.error = null;

    $scope.registerWithFacebook = function() {
        facebookConnect.askFacebookForAuthentication(
        function(reason) { // fail
            $scope.error = reason;
        }, function(user) { // success
            $scope.user = user
            $scope.$digest() // Manual scope evaluation
        });
    }
}

JSFiddle

Pertanyaannya adalah:

  • Apa perbedaan dalam contoh di atas?
  • Apa alasan dan kasus untuk menggunakan layanan $ q ?
  • Dan bagaimana cara kerjanya ?
Maksym
sumber
9
Kedengarannya seperti Anda harus membaca tentang apa itu janji, dan mengapa mereka digunakan secara umum ... mereka tidak eksklusif untuk sudut dan ada banyak bahan yang tersedia
charlietfl
1
@charlietfl, poin bagus, tapi saya mengharapkan jawaban yang rumit yang akan mencakup keduanya: mengapa mereka digunakan secara umum dan bagaimana menggunakannya di Angular. Terima kasih atas saran Anda
Maksym

Jawaban:

401

Ini tidak akan menjadi jawaban lengkap untuk pertanyaan Anda, tetapi mudah-mudahan ini akan membantu Anda dan orang lain ketika Anda mencoba membaca dokumentasi pada $qlayanan. Butuh beberapa saat untuk memahaminya.

Mari kita singkirkan AngularJS sejenak dan pertimbangkan saja panggilan Facebook API. Kedua panggilan API menggunakan mekanisme panggilan balik untuk memberi tahu penelepon ketika respons dari Facebook tersedia:

  facebook.FB.api('/' + item, function (result) {
    if (result.error) {
      // handle error
    } else {
      // handle success
    }
  });
  // program continues while request is pending
  ...

Ini adalah pola standar untuk menangani operasi asinkron dalam JavaScript dan bahasa lainnya.

Satu masalah besar dengan pola ini muncul ketika Anda perlu melakukan urutan operasi asinkron, di mana setiap operasi berturut-turut tergantung pada hasil operasi sebelumnya. Itulah yang dilakukan kode ini:

  FB.login(function(response) {
      if (response.authResponse) {
          FB.api('/me', success);
      } else {
          fail('User cancelled login or did not fully authorize.');
      }
  });

Pertama mencoba masuk, dan kemudian hanya setelah memverifikasi bahwa login berhasil apakah itu membuat permintaan ke API Grafik.

Bahkan dalam kasus ini, yang hanya menggabungkan dua operasi, semuanya mulai menjadi berantakan. Metode askFacebookForAuthenticationmenerima panggilan balik untuk kegagalan dan kesuksesan, tetapi apa yang terjadi ketika FB.loginberhasil tetapi FB.apigagal? Metode ini selalu memanggil successbalik tanpa memperhatikan hasil dari FB.apimetode.

Sekarang bayangkan Anda mencoba untuk mengode urutan kuat dari tiga atau lebih operasi asinkron, dengan cara yang benar menangani kesalahan pada setiap langkah dan akan dapat dibaca oleh orang lain atau bahkan untuk Anda setelah beberapa minggu. Mungkin, tapi sangat mudah untuk tetap bersarang panggilan balik itu dan kehilangan jejak kesalahan di sepanjang jalan.

Sekarang, mari kita singkirkan API Facebook sejenak dan pertimbangkan saja Angular Promises API, seperti yang diterapkan oleh $qlayanan. Pola yang diterapkan oleh layanan ini adalah upaya untuk mengubah pemrograman asinkron menjadi sesuatu yang menyerupai serangkaian pernyataan linier sederhana, dengan kemampuan untuk 'melempar' kesalahan pada langkah apa pun dan menanganinya di akhir, secara semantik mirip dengan try/catchblok akrab .

Pertimbangkan contoh yang dibuat-buat ini. Katakanlah kita memiliki dua fungsi, di mana fungsi kedua mengkonsumsi hasil dari yang pertama:

 var firstFn = function(param) {
    // do something with param
    return 'firstResult';
 };

 var secondFn = function(param) {
    // do something with param
    return 'secondResult';
 };

 secondFn(firstFn()); 

Sekarang bayangkan bahwa firstFn dan secondFn membutuhkan waktu lama untuk diselesaikan, jadi kami ingin memproses urutan ini secara tidak sinkron. Pertama, kita membuat deferredobjek baru , yang mewakili rantai operasi:

 var deferred = $q.defer();
 var promise = deferred.promise;

The promiseproperti merupakan hasil akhir dari rantai. Jika Anda mencatat janji segera setelah membuatnya, Anda akan melihat bahwa itu hanyalah objek kosong ( {}). Belum ada yang bisa dilihat, terus bergerak.

Sejauh ini janji kami hanya mewakili titik awal dalam rantai. Sekarang mari kita tambahkan dua operasi kami:

 promise = promise.then(firstFn).then(secondFn);

The thenMetode menambahkan langkah untuk rantai dan kemudian kembali janji baru yang mewakili hasil akhir dari rantai diperpanjang. Anda dapat menambahkan langkah sebanyak yang Anda mau.

Sejauh ini, kami telah mengatur rantai fungsi kami, tetapi tidak ada yang benar-benar terjadi. Anda memulai sesuatu dengan menelepon deferred.resolve, menentukan nilai awal yang ingin Anda sampaikan ke langkah aktual pertama dalam rantai:

 deferred.resolve('initial value');

Dan kemudian ... masih tidak ada yang terjadi. Untuk memastikan bahwa perubahan model diamati dengan benar, Angular tidak benar-benar memanggil langkah pertama dalam rantai sampai waktu berikutnya $applydisebut:

 deferred.resolve('initial value');
 $rootScope.$apply();

 // or     
 $rootScope.$apply(function() {
    deferred.resolve('initial value');
 });

Jadi bagaimana dengan penanganan kesalahan? Sejauh ini kami hanya menetapkan penangan sukses di setiap langkah dalam rantai. thenjuga menerima penangan kesalahan sebagai argumen kedua opsional. Inilah contoh lain dari rantai janji yang lebih panjang, kali ini dengan penanganan kesalahan:

 var firstFn = function(param) {
    // do something with param
    if (param == 'bad value') {
      return $q.reject('invalid value');
    } else {
      return 'firstResult';
    }
 };

 var secondFn = function(param) {
    // do something with param
    if (param == 'bad value') {
      return $q.reject('invalid value');
    } else {
      return 'secondResult';
    }
 };

 var thirdFn = function(param) {
    // do something with param
    return 'thirdResult';
 };

 var errorFn = function(message) {
   // handle error
 };

 var deferred = $q.defer();
 var promise = deferred.promise.then(firstFn).then(secondFn).then(thirdFn, errorFn);

Seperti yang dapat Anda lihat dalam contoh ini, setiap penangan dalam rantai memiliki kesempatan untuk mengalihkan lalu lintas ke penangan kesalahan berikutnya alih-alih penangan keberhasilan berikutnya . Dalam kebanyakan kasus, Anda dapat memiliki penangan kesalahan tunggal di akhir rantai, tetapi Anda juga dapat memiliki penangan kesalahan menengah yang mencoba pemulihan.

Untuk segera kembali ke contoh Anda (dan pertanyaan Anda), saya hanya akan mengatakan bahwa mereka mewakili dua cara berbeda untuk mengadaptasi API berorientasi panggilan balik Facebook dengan cara Angular mengamati perubahan model. Contoh pertama membungkus panggilan API dalam sebuah janji, yang dapat ditambahkan ke ruang lingkup dan dipahami oleh sistem templating Angular. Yang kedua mengambil pendekatan yang lebih kasar dari pengaturan hasil panggilan balik langsung pada ruang lingkup, dan kemudian panggilan $scope.$digest()untuk membuat Angular menyadari perubahan dari sumber eksternal.

Kedua contoh tidak dapat dibandingkan secara langsung, karena yang pertama tidak memiliki langkah login. Namun, umumnya diinginkan untuk merangkum interaksi dengan API eksternal seperti ini di layanan terpisah, dan memberikan hasilnya kepada pengontrol sesuai janji. Dengan begitu Anda dapat memisahkan pengontrol Anda dari masalah eksternal, dan mengujinya dengan lebih mudah dengan layanan tiruan.

karlgold
sumber
5
Saya pikir ini jawaban yang bagus! Hal utama, bagi saya, adalah menggambarkan kasus umum ketika janji benar-benar aktual. Jujur saya berharap untuk contoh nyata lain (seperti halnya dengan Facebook), tetapi ini juga berfungsi saya kira. Terimakasih banyak!
Maksym
2
Alternatif untuk merantai beberapa thenmetode adalah menggunakan $q.all. Tutorial singkat tentang hal itu dapat ditemukan di sini .
Bogdan
2
$q.allsesuai jika Anda perlu menunggu beberapa operasi asinkron independen untuk menyelesaikan. Itu tidak menggantikan rantai jika setiap operasi tergantung pada hasil dari operasi sebelumnya.
karlgold
1
rantai kemudian dijelaskan secara ringkas di sini. Membantu saya memahami dan menggunakannya secara maksimal. Terima kasih
Tushar Joshi
1
Jawaban yang bagus @karlgold! Saya mempunyai satu pertanyaan. Jika, dalam cuplikan kode terakhir, Anda mengubah return 'firstResult'bagian menjadi return $q.resolve('firstResult'), apa bedanya?
technophyle
9

Saya mengharapkan jawaban yang kompleks yang akan mencakup keduanya: mengapa mereka digunakan secara umum dan bagaimana menggunakannya di Angular

Ini adalah bungkusan untuk janji sudut MVP ( janji minimum yang layak) : http://plnkr.co/edit/QBAB0usWXc96TnxqKhuA?p=preview

Sumber:

(bagi mereka yang terlalu malas untuk mengklik tautan)

index.html

  <head>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.1.5/angular.js"></script>
    <script src="app.js"></script>
  </head>

  <body ng-app="myModule" ng-controller="HelloCtrl">
    <h1>Messages</h1>
    <ul>
      <li ng-repeat="message in messages">{{ message }}</li>
    </ul>
  </body>

</html>

app.js

angular.module('myModule', [])

  .factory('HelloWorld', function($q, $timeout) {

    var getMessages = function() {
      var deferred = $q.defer();

      $timeout(function() {
        deferred.resolve(['Hello', 'world']);
      }, 2000);

      return deferred.promise;
    };

    return {
      getMessages: getMessages
    };

  })

  .controller('HelloCtrl', function($scope, HelloWorld) {

    $scope.messages = HelloWorld.getMessages();

  });

(Saya tahu itu tidak menyelesaikan contoh Facebook spesifik Anda, tetapi saya menemukan cuplikan berikut berguna)

Via: http://markdalgleish.com/2013/06/using-promises-in-angularjs-views/


Pembaruan 28 Februari 2014: Mulai 1.2.0, janji tidak lagi diselesaikan oleh templat. http://www.benlesh.com/2013/02/angularjs-creating-service-with-http.html

(contoh plunker menggunakan 1.1.5.)

Mars Robertson
sumber
afaik kita cintai karena kita malas
mkb
ini membantu saya memahami $ q, ditunda dan dirantai. lalu panggilan fungsi, jadi terima kasih.
aliopi
1

Ditangguhkan merupakan hasil dari operasi asinkronis. Itu memperlihatkan antarmuka yang dapat digunakan untuk memberi sinyal keadaan dan hasil operasi yang diwakilinya. Ini juga menyediakan cara untuk mendapatkan instance janji terkait.

Sebuah janji menyediakan antarmuka untuk berinteraksi dengan itu terkait ditangguhkan, dan dengan demikian, memungkinkan pihak yang berkepentingan untuk mendapatkan akses ke negara dan hasil dari operasi yang ditangguhkan.

Saat membuat tangguhan, statusnya sedang menunggu dan tidak ada hasilnya. Saat kami menyelesaikan () atau menolak () yang ditangguhkan, ia mengubah statusnya untuk diselesaikan atau ditolak. Namun, kami bisa mendapatkan janji terkait segera setelah membuat ditangguhkan dan bahkan menetapkan interaksi dengan hasil di masa depan. Interaksi itu akan terjadi hanya setelah ditangguhkan ditolak atau diselesaikan.

Ram G
sumber
1

gunakan janji dalam pengontrol dan pastikan datanya tersedia atau tidak

 var app = angular.module("app",[]);
      
      app.controller("test",function($scope,$q){
        var deferred = $q.defer();
        deferred.resolve("Hi");
        deferred.promise.then(function(data){
        console.log(data);    
        })
      });
      angular.bootstrap(document,["app"]);
<!DOCTYPE html>
<html>

  <head>
    <script data-require="angular.js@*" data-semver="1.3.0-beta.5" src="https://code.angularjs.org/1.3.0-beta.5/angular.js"></script>
  </head>

  <body>
    <h1>Hello Angular</h1>
    <div ng-controller="test">
    </div>
  </body>

</html>

Manivannan A
sumber