ใช้ความสำเร็จ / ข้อผิดพลาด / สุดท้าย / จับด้วยสัญญาใน AngularJS


112

ฉันกำลังใช้ $httpใน AngularJs และฉันไม่แน่ใจเกี่ยวกับวิธีใช้สัญญาที่ส่งคืนและจัดการข้อผิดพลาด

ฉันมีรหัสนี้:

$http
    .get(url)
    .success(function(data) {
        // Handle data
    })
    .error(function(data, status) {
        // Handle HTTP error
    })
    .finally(function() {
        // Execute logic independent of success/error
    })
    .catch(function(error) {
        // Catch and handle exceptions from success/error/finally functions
    });

วิธีนี้เป็นวิธีที่ดีหรือมีวิธีที่ง่ายกว่านี้ไหม

คำตอบ:


103

คำสัญญาเป็นสิ่งที่เป็นนามธรรมเหนือข้อความที่ช่วยให้เราสามารถแสดงตัวตนพร้อมกันกับรหัสอะซิงโครนัสได้ เป็นตัวแทนของการดำเนินงานครั้งเดียว

นอกจากนี้ยังมีการจัดการข้อยกเว้นเช่นเดียวกับรหัสปกติคุณสามารถกลับมาจากคำสัญญาหรือโยนได้

สิ่งที่คุณต้องการในรหัสซิงโครนัสคือ:

try{
  try{
      var res = $http.getSync("url");
      res = someProcessingOf(res);
  } catch (e) {
      console.log("Got an error!",e);
      throw e; // rethrow to not marked as handled
  }
  // do more stuff with res
} catch (e){
     // handle errors in processing or in error.
}

เวอร์ชันที่ได้รับการรับรองนั้นคล้ายกันมาก:

$http.get("url").
then(someProcessingOf).
catch(function(e){
   console.log("got an error in initial processing",e);
   throw e; // rethrow to not marked as handled, 
            // in $q it's better to `return $q.reject(e)` here
}).then(function(res){
    // do more stuff
}).catch(function(e){
    // handle errors in processing or in error.
});

4
คุณจะใช้success()อย่างไรerror()และfinally()รวมกับcatch()? หรือฉันต้องใช้then(successFunction, errorFunction).catch(exceotionHandling).then(cleanUp);
Joel

3
@Joel โดยทั่วไปคุณไม่ต้องการที่จะใช้ที่เคยsuccessและerror(ต้องการ.thenและ.catchแทนคุณสามารถ (และควร) ละเว้นerrorFunctionจาก.thenการใช้ AC catchเหมือนในรหัสของฉันข้างต้น)
Benjamin Gruenbaum

@BenjaminGruenbaum คุณสามารถอธิบายได้อย่างละเอียดว่าทำไมคุณถึงแนะนำให้หลีกเลี่ยงsuccess/ error? นอกจากนี้ Eclipse ของฉันยังทำงาน amok เมื่อมันเห็น.catch(ดังนั้นฉันจึงใช้["catch"](ตอนนี้ ฉันจะเชื่องคราสได้อย่างไร?
Giszmo

การใช้งานโมดูล $ http ของ Angular ของไลบรารี $ q ใช้ .success และ. error แทน. แล้วและ. catch อย่างไรก็ตามในการทดสอบของฉันฉันสามารถเข้าถึงคุณสมบัติทั้งหมดของสัญญา $ http เมื่อใช้สัญญา. แล้วและ. ดูคำตอบของ zd333 ด้วย
Steve K

3
@SirBenBenji $ Q ไม่ได้.successและ.error, $ http ส่งกลับสัญญา $ Q ด้วยนอกจากนี้ของsuccessและerrorขนย้าย - แต่รถขนเหล่านี้ไม่ห่วงโซ่และโดยทั่วไปควรหลีกเลี่ยงถ้า / เมื่อเป็นไปได้ โดยทั่วไป - หากคุณมีคำถามควรถามเป็นคำถามใหม่ไม่ใช่เป็นการแสดงความคิดเห็นเกี่ยวกับคำถามเก่า
Benjamin Gruenbaum

43

ลืมเกี่ยวกับการใช้successและerrorวิธีการ

ทั้งสองวิธีนี้เลิกใช้แล้วในเชิงมุม 1.4 โดยพื้นฐานแล้วเหตุผลที่อยู่เบื้องหลังการเลิกใช้งานก็คือพวกเขาไม่ได้เป็นมิตรกับลูกโซ่ดังนั้นที่จะพูด

กับตัวอย่างต่อไปนี้ผมจะพยายามที่จะแสดงให้เห็นถึงสิ่งที่ฉันหมายถึงเกี่ยวกับsuccessและerrorไม่ถูกchainable ง่าย สมมติว่าเราเรียก API ที่ส่งคืนวัตถุผู้ใช้ด้วยที่อยู่:

วัตถุของผู้ใช้:

{name: 'Igor', address: 'San Francisco'}

โทรไปที่ API:

$http.get('/user')
    .success(function (user) {
        return user.address;   <---  
    })                            |  // you might expect that 'obj' is equal to the
    .then(function (obj) {   ------  // address of the user, but it is NOT

        console.log(obj); // -> {name: 'Igor', address: 'San Francisco'}
    });
};

เกิดอะไรขึ้น?

เพราะsuccessและerrorส่งคืนสัญญาเดิมนั่นคือสิ่งที่ส่งคืนโดย$http.getวัตถุที่ส่งผ่านไปยังการเรียกกลับของthenคือวัตถุผู้ใช้ทั้งหมดกล่าวคือการป้อนข้อมูลเดียวกันกับสิ่งที่อยู่ก่อนหน้าsuccessเรียกกลับนี้

ถ้าเราล่ามโซ่สองคนthenสิ่งนี้จะทำให้สับสนน้อยลง:

$http.get('/user')
    .then(function (user) {
        return user.address;  
    })
    .then(function (obj) {  
        console.log(obj); // -> 'San Francisco'
    });
};

1
นอกจากนี้ควรสังเกตว่าsuccessและerrorจะถูกเพิ่มเข้าไปในการ$httpโทรกลับทันที (ไม่ใช่ต้นแบบ) ดังนั้นหากคุณเรียกใช้วิธีการสัญญาอื่นระหว่างพวกเขา (เช่นปกติคุณจะเรียกรวมreturn $http.get(url)อยู่ในไลบรารีฐาน แต่ในภายหลังตัดสินใจที่จะสลับสปินเนอร์ใน ห้องสมุดเรียกด้วยreturn $http.get(url).finally(...)) คุณจะไม่มีวิธีอำนวยความสะดวกเหล่านั้นอีกต่อไป
drzaus

35

ฉันคิดว่าคำตอบก่อนหน้านี้ถูกต้อง แต่นี่เป็นอีกตัวอย่างหนึ่ง (เพียงแค่ fyi, success () และ error () ถูกเลิกใช้ตามหน้าหลัก AngularJS :

$http
    .get('http://someendpoint/maybe/returns/JSON')
    .then(function(response) {
        return response.data;
    }).catch(function(e) {
        console.log('Error: ', e);
        throw e;
    }).finally(function() {
        console.log('This finally block');
    });

3
ในที่สุดก็ไม่คืนการตอบสนองต่อความรู้ของฉัน
diplosaurus

11

คุณกำลังมองหารายละเอียดประเภทใด โดยทั่วไปคุณสามารถทำได้โดย:

$http.get(url).then(
  //success function
  function(results) {
    //do something w/results.data
  },
  //error function
  function(err) {
    //handle error
  }
);

ฉันพบว่า "ในที่สุด" และ "จับใจ" จะดีกว่าเมื่อผูกมัดคำสัญญาหลาย ๆ


1
ในตัวอย่างของคุณตัวจัดการข้อผิดพลาดจะจัดการเฉพาะข้อผิดพลาด $ http
Benjamin Gruenbaum

1
ใช่ฉันยังต้องจัดการข้อยกเว้นในฟังก์ชันความสำเร็จ / ข้อผิดพลาดด้วย แล้วฉันก็ต้องการตัวจัดการทั่วไป (ที่ฉันสามารถตั้งค่าต่างๆเช่นloading = false)
Joel

1
คุณมีวงเล็บปีกกาแทนวงเล็บปิดการโทรแล้ว () ของคุณ
Paul McClean

1
สิ่งนี้ใช้ไม่ได้กับข้อผิดพลาดในการตอบสนอง 404 ใช้ได้กับ.catch()Method เท่านั้น
elporfirio

นี่คือคำตอบที่ถูกต้องสำหรับการจัดการข้อผิดพลาด http ที่ส่งกลับไปยังคอนโทรลเลอร์
Leon

5

ในกรณี Angular $ http ฟังก์ชัน success () และ error () จะมีการปิดกั้นวัตถุตอบกลับดังนั้นลายเซ็นการเรียกกลับจะเป็นเหมือน $ http (... ). success (function (data, status, headers, config))

สำหรับแล้ว () คุณอาจจะจัดการกับวัตถุตอบสนองดิบ เช่นโพสต์ในเอกสาร AngularJS $ http API

$http({
        url: $scope.url,
        method: $scope.method,
        cache: $templateCache
    })
    .success(function(data, status) {
        $scope.status = status;
        $scope.data = data;
    })
    .error(function(data, status) {
        $scope.data = data || 'Request failed';
        $scope.status = status;
    });

.catch (... ) สุดท้ายจะไม่ต้องการเว้นแต่จะมีข้อผิดพลาดใหม่เกิดขึ้นในห่วงโซ่สัญญาก่อนหน้านี้


2
เลิกใช้วิธีการสำเร็จ / ข้อผิดพลาด
OverMars

-3

ฉันทำเหมือน Bradley Braithwaite แนะนำในบล็อกของเขา:

app
    .factory('searchService', ['$q', '$http', function($q, $http) {
        var service = {};

        service.search = function search(query) {
            // We make use of Angular's $q library to create the deferred instance
            var deferred = $q.defer();

            $http
                .get('http://localhost/v1?=q' + query)
                .success(function(data) {
                    // The promise is resolved once the HTTP call is successful.
                    deferred.resolve(data);
                })
                .error(function(reason) {
                    // The promise is rejected if there is an error with the HTTP call.
                    deferred.reject(reason);
                });

            // The promise is returned to the caller
            return deferred.promise;
        };

        return service;
    }])
    .controller('SearchController', ['$scope', 'searchService', function($scope, searchService) {
        // The search service returns a promise API
        searchService
            .search($scope.query)
            .then(function(data) {
                // This is set when the promise is resolved.
                $scope.results = data;
            })
            .catch(function(reason) {
                // This is set in the event of an error.
                $scope.error = 'There has been an error: ' + reason;
            });
    }])

ประเด็นสำคัญ:

  • ฟังก์ชันแก้ไขจะเชื่อมโยงไปยังฟังก์ชัน. then ในคอนโทรลเลอร์ของเรานั่นคือทุกอย่างเรียบร้อยดีดังนั้นเราจึงสามารถรักษาสัญญาและแก้ไขได้

  • ฟังก์ชันปฏิเสธจะเชื่อมโยงไปยังฟังก์ชัน. จับในตัวควบคุมของเรานั่นคือมีบางอย่างผิดพลาดเราจึงไม่สามารถรักษาสัญญาและจำเป็นต้องปฏิเสธมัน

ค่อนข้างเสถียรและปลอดภัยและหากคุณมีเงื่อนไขอื่น ๆ ที่จะปฏิเสธคำสัญญาคุณสามารถกรองข้อมูลของคุณในฟังก์ชันความสำเร็จและโทรหาdeferred.reject(anotherReason)ด้วยเหตุผลของการปฏิเสธได้

ตามที่ Ryan Vice แนะนำในความคิดเห็นสิ่งนี้อาจไม่ถูกมองว่ามีประโยชน์เว้นแต่คุณจะตอบสนองเล็กน้อยเพื่อที่จะพูด

เพราะsuccessและerrorมีการเลิกใช้ตั้งแต่ 1.4 บางทีมันอาจจะดีกว่าที่จะใช้วิธีการสัญญาปกติthenและcatchและเปลี่ยนการตอบสนองภายในวิธีการเหล่านั้นและกลับสัญญาของการตอบสนองที่เปลี่ยน

ฉันกำลังแสดงตัวอย่างเดียวกันกับทั้งสองวิธีและวิธีที่สามระหว่างกัน:

successและerrorวิธีการ ( successและerrorส่งคืนสัญญาของการตอบสนอง HTTP ดังนั้นเราต้องการความช่วยเหลือใน$qการคืนสัญญาข้อมูล):

function search(query) {
  // We make use of Angular's $q library to create the deferred instance
  var deferred = $q.defer();

  $http.get('http://localhost/v1?=q' + query)
  .success(function(data,status) {
    // The promise is resolved once the HTTP call is successful.
    deferred.resolve(data);              
  })

  .error(function(reason,status) {
    // The promise is rejected if there is an error with the HTTP call.
    if(reason.error){
      deferred.reject({text:reason.error, status:status});
    }else{
      //if we don't get any answers the proxy/api will probably be down
      deferred.reject({text:'whatever', status:500});
    }
  });

  // The promise is returned to the caller
  return deferred.promise;
};

thenและcatchเข้าใกล้ (นี่เป็นการทดสอบที่ยากกว่าเล็กน้อยเนื่องจากการขว้าง):

function search(query) {

  var promise=$http.get('http://localhost/v1?=q' + query)

  .then(function (response) {
    // The promise is resolved once the HTTP call is successful.
    return response.data;
  },function(reason) {
    // The promise is rejected if there is an error with the HTTP call.
    if(reason.statusText){
      throw reason;
    }else{
      //if we don't get any answers the proxy/api will probably be down
      throw {statusText:'Call error', status:500};
    }

  });

  return promise;
}

มีวิธีแก้ปัญหาครึ่งทาง (วิธีนี้คุณสามารถหลีกเลี่ยงthrowและอย่างไรก็ตามคุณอาจต้องใช้$qเพื่อเยาะเย้ยพฤติกรรมสัญญาในการทดสอบของคุณ):

function search(query) {
  // We make use of Angular's $q library to create the deferred instance
  var deferred = $q.defer();

  $http.get('http://localhost/v1?=q' + query)

  .then(function (response) {
    // The promise is resolved once the HTTP call is successful.
    deferred.resolve(response.data);
  },function(reason) {
    // The promise is rejected if there is an error with the HTTP call.
    if(reason.statusText){
      deferred.reject(reason);
    }else{
      //if we don't get any answers the proxy/api will probably be down
      deferred.reject({statusText:'Call error', status:500});
    }

  });

  // The promise is returned to the caller
  return deferred.promise;
}

ยินดีรับฟังความคิดเห็นหรือการแก้ไขใด ๆ


2
ทำไมคุณถึงใช้ $ q เพื่อห่อสัญญาด้วยคำมั่นสัญญา ทำไมไม่เพียงแค่คืนสัญญาที่ส่งคืนโดย $ http.get ()?
Ryan Vice

เพราะsuccess()และerror()จะไม่คืนสัญญาใหม่เหมือนthen()เดิม ด้วย$qเราทำให้โรงงานของเราส่งคืนสัญญาของข้อมูลแทนที่จะเป็นสัญญาของการตอบสนอง HTTP
ช่างซ่อมนาฬิกา

คำตอบของคุณทำให้ฉันสับสนดังนั้นบางทีฉันอาจอธิบายตัวเองไม่เก่ง เว้นแต่คุณจะจัดการกับการตอบสนองคุณก็สามารถคืนสัญญาที่ $ http ส่งคืนได้ ดูตัวอย่างนี้ที่ฉันเพิ่งเขียน: jsbin.com/belagan/edit?html,js,output
Ryan Vice

1
ฉันไม่เห็นค่า. รู้สึกว่าไม่จำเป็นสำหรับฉันและฉันปฏิเสธการตรวจสอบโค้ดในโครงการของฉันที่ใช้แนวทางนี้ แต่ถ้าคุณได้รับคุณค่าจากมันคุณควรใช้มัน ฉันยังเห็นคำสัญญาบางอย่างในบทความแนวปฏิบัติที่ดีที่สุดเชิงมุมที่เรียกร้องให้ห่อโดยไม่จำเป็นว่าเป็นกลิ่น
Ryan Vice

1
นี่คือการรอการตัดบัญชีต่อต้านรูปแบบ อ่านYou're Missing the Point of Promises
georgeawg
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.