นี่ไม่ใช่คำตอบที่สมบูรณ์สำหรับคำถามของคุณ แต่หวังว่าจะช่วยให้คุณและผู้อื่นเมื่อคุณพยายามอ่านเอกสารใน$q
บริการ ฉันใช้เวลาสักครู่เพื่อทำความเข้าใจ
ลองแยก AngularJS สักครู่แล้วลองพิจารณาการโทรผ่าน Facebook API การเรียก API ทั้งสองใช้กลไกการโทรกลับเพื่อแจ้งผู้โทรเมื่อมีการตอบกลับจาก Facebook:
facebook.FB.api('/' + item, function (result) {
if (result.error) {
// handle error
} else {
// handle success
}
});
// program continues while request is pending
...
นี่เป็นรูปแบบมาตรฐานสำหรับการจัดการการดำเนินการแบบอะซิงโครนัสใน JavaScript และภาษาอื่น ๆ
ปัญหาใหญ่ที่เกิดขึ้นกับรูปแบบนี้เกิดขึ้นเมื่อคุณต้องการดำเนินการลำดับของการดำเนินการแบบอะซิงโครนัสซึ่งการดำเนินการที่ต่อเนื่องแต่ละครั้งขึ้นอยู่กับผลลัพธ์ของการดำเนินการก่อนหน้านี้ นั่นคือสิ่งที่รหัสนี้กำลังทำ:
FB.login(function(response) {
if (response.authResponse) {
FB.api('/me', success);
} else {
fail('User cancelled login or did not fully authorize.');
}
});
ก่อนอื่นก็พยายามเข้าสู่ระบบและหลังจากตรวจสอบแล้วว่าการเข้าสู่ระบบสำเร็จนั้นจะทำการร้องขอไปยังกราฟ API
แม้ในกรณีนี้ซึ่งเป็นการผูกมัดเพียงสองอย่างเข้าด้วยกันสิ่งต่าง ๆ ก็เริ่มยุ่งเหยิง วิธีนี้askFacebookForAuthentication
ยอมรับการเรียกกลับสำหรับความล้มเหลวและความสำเร็จ แต่เกิดอะไรขึ้นเมื่อFB.login
สำเร็จ แต่FB.api
ล้มเหลว วิธีการนี้จะเรียกการsuccess
เรียกกลับโดยไม่คำนึงถึงผลของFB.api
วิธีการ
ตอนนี้ลองนึกภาพว่าคุณกำลังพยายามเขียนรหัสลำดับที่มีประสิทธิภาพของการทำงานแบบอะซิงโครนัสตั้งแต่สามครั้งขึ้นไปในวิธีที่จัดการข้อผิดพลาดได้อย่างถูกต้องในแต่ละขั้นตอนและสามารถอ่านได้กับคนอื่น ๆ เป็นไปได้ แต่เป็นเรื่องง่ายมากที่จะเพียงแค่วางซ้อนการเรียกกลับเหล่านั้นและติดตามข้อผิดพลาดไปพร้อมกัน
ทีนี้ลองแยก Facebook API สักครู่แล้วลองพิจารณา Angular Promises API ตามที่$q
บริการดำเนินการ รูปแบบที่ดำเนินการโดยบริการนี้เป็นความพยายามที่จะเปลี่ยนการเขียนโปรแกรมแบบอะซิงโครนัสกลับไปเป็นสิ่งที่คล้ายกับชุดข้อความเชิงเส้นตรงซึ่งมีความสามารถในการ 'โยน' ข้อผิดพลาดในขั้นตอนใด ๆtry/catch
บล็อกที่คุ้นเคย
ลองพิจารณาตัวอย่างที่ประดิษฐ์ขึ้นนี้ สมมติว่าเรามีสองฟังก์ชันโดยที่ฟังก์ชันที่สองใช้ผลลัพธ์ของฟังก์ชันแรก:
var firstFn = function(param) {
// do something with param
return 'firstResult';
};
var secondFn = function(param) {
// do something with param
return 'secondResult';
};
secondFn(firstFn());
ทีนี้ลองนึกภาพว่า firstFn และ secondFn ทั้งคู่ใช้เวลานานกว่าจะเสร็จสมบูรณ์ดังนั้นเราจึงต้องการประมวลผลลำดับนี้แบบอะซิงโครนัส ครั้งแรกที่เราสร้างdeferred
วัตถุใหม่ซึ่งแสดงถึงห่วงโซ่ของการดำเนินการ:
var deferred = $q.defer();
var promise = deferred.promise;
promise
ทรัพย์สินหมายถึงผลที่สุดของห่วงโซ่ หากคุณบันทึกสัญญาทันทีหลังจากสร้างคุณจะเห็นว่าเป็นเพียงวัตถุว่างเปล่า ( {}
) ยังไม่มีอะไรให้ดูเลยไปทางขวา
จนถึงคำสัญญาของเราเพียงแสดงจุดเริ่มต้นในห่วงโซ่ ทีนี้ลองเพิ่มการดำเนินการสองอย่างของเรา:
promise = promise.then(firstFn).then(secondFn);
then
วิธีการเพิ่มขั้นตอนในการห่วงโซ่และจากนั้นส่งกลับสัญญาใหม่ที่เป็นตัวแทนของผลที่สุดของห่วงโซ่การขยาย คุณสามารถเพิ่มขั้นตอนได้มากเท่าที่คุณต้องการ
จนถึงตอนนี้เราได้ตั้งค่าฟังก์ชั่นโซ่ แต่ก็ไม่มีอะไรเกิดขึ้นจริง คุณได้รับสิ่งที่เริ่มต้นด้วยการโทรdeferred.resolve
ระบุค่าเริ่มต้นที่คุณต้องการผ่านไปยังขั้นตอนแรกที่แท้จริงในห่วงโซ่:
deferred.resolve('initial value');
แล้ว ... ยังไม่มีอะไรเกิดขึ้น เพื่อให้แน่ใจว่ามีการตรวจสอบการเปลี่ยนแปลงรูปแบบอย่างเหมาะสม Angular ไม่ได้เรียกขั้นตอนแรกในห่วงโซ่จริงจนกว่า$apply
จะมีการเรียกใช้ครั้งต่อไป:
deferred.resolve('initial value');
$rootScope.$apply();
// or
$rootScope.$apply(function() {
deferred.resolve('initial value');
});
แล้วการจัดการข้อผิดพลาดล่ะ? จนถึงตอนนี้เราได้ระบุตัวจัดการความสำเร็จในแต่ละขั้นตอนในห่วงโซ่เท่านั้น then
ยังยอมรับตัวจัดการข้อผิดพลาดเป็นอาร์กิวเมนต์ตัวเลือกที่สอง นี่คืออีกตัวอย่างหนึ่งที่ยาวกว่าของเครือสัญญาในครั้งนี้ที่มีการจัดการข้อผิดพลาด:
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);
อย่างที่คุณเห็นในตัวอย่างนี้แต่ละ handler ใน chain มีโอกาสที่จะเบี่ยงเบนการรับส่งข้อมูลไปยังตัวจัดการข้อผิดพลาดถัดไปแทนที่จะเป็นhandler ที่ประสบความสำเร็จต่อไป ในกรณีส่วนใหญ่คุณสามารถมีตัวจัดการข้อผิดพลาดเดียวที่ส่วนท้ายของเชน แต่คุณสามารถมีตัวจัดการข้อผิดพลาดระดับกลางที่พยายามกู้คืน
หากต้องการกลับไปที่ตัวอย่างของคุณ (และคำถามของคุณ) อย่างรวดเร็วฉันจะบอกว่าพวกเขาเป็นตัวแทนของสองวิธีที่แตกต่างกันในการปรับ API การติดต่อกลับของ Facebook ให้สอดคล้องกับวิธีของ Angular ในการสังเกตการเปลี่ยนแปลงรูปแบบ ตัวอย่างแรกล้อมรอบการเรียก API ในสัญญาซึ่งสามารถเพิ่มในขอบเขตและเข้าใจได้โดยระบบ templating ของ Angular ครั้งที่สองใช้วิธีการบังคับเดรัจฉานมากขึ้นในการตั้งค่าผลลัพธ์การโทรกลับโดยตรงบนขอบเขตแล้วเรียก$scope.$digest()
ให้ Angular รับรู้การเปลี่ยนแปลงจากแหล่งภายนอก
ตัวอย่างสองตัวอย่างนั้นไม่สามารถเปรียบเทียบกันได้โดยตรงเนื่องจากขั้นตอนแรกไม่มีขั้นตอนการลงชื่อเข้าใช้ อย่างไรก็ตามโดยทั่วไปเป็นที่ต้องการในการสรุปการโต้ตอบกับ API ภายนอกเช่นนี้ในบริการแยกต่างหากและส่งมอบผลลัพธ์ให้กับตัวควบคุมตามที่สัญญาไว้ ด้วยวิธีนี้คุณสามารถแยกตัวควบคุมออกจากข้อกังวลภายนอกและทดสอบได้ง่ายขึ้นด้วยบริการจำลอง