Đây sẽ không phải là một câu trả lời hoàn chỉnh cho câu hỏi của bạn, nhưng hy vọng điều này sẽ giúp bạn và những người khác khi bạn cố gắng đọc tài liệu về $q
dịch vụ. Phải mất một thời gian để hiểu nó.
Hãy tạm gác AngularJS một chút và chỉ xem xét các lệnh gọi API của Facebook. Cả hai lệnh gọi API đều sử dụng cơ chế gọi lại để thông báo cho người gọi khi có phản hồi từ Facebook:
facebook.FB.api('/' + item, function (result) {
if (result.error) {
// handle error
} else {
// handle success
}
});
// program continues while request is pending
...
Đây là một mẫu chuẩn để xử lý các hoạt động không đồng bộ trong JavaScript và các ngôn ngữ khác.
Một vấn đề lớn với mẫu này phát sinh khi bạn cần thực hiện một chuỗi các hoạt động không đồng bộ, trong đó mỗi hoạt động liên tiếp phụ thuộc vào kết quả của hoạt động trước đó. Đó là những gì mã này đang làm:
FB.login(function(response) {
if (response.authResponse) {
FB.api('/me', success);
} else {
fail('User cancelled login or did not fully authorize.');
}
});
Đầu tiên, nó cố gắng đăng nhập và sau đó chỉ sau khi xác minh rằng đăng nhập thành công, nó mới thực hiện yêu cầu đối với API đồ thị.
Ngay cả trong trường hợp này, chỉ kết hợp hai thao tác, mọi thứ bắt đầu trở nên lộn xộn. Phương pháp askFacebookForAuthentication
chấp nhận một cuộc gọi lại cho thất bại và thành công, nhưng điều gì xảy ra khi FB.login
thành công nhưng FB.api
thất bại? Phương thức này luôn gọi hàm success
gọi lại bất kể kết quả của FB.api
phương thức.
Bây giờ hãy tưởng tượng rằng bạn đang cố mã hóa một chuỗi mạnh mẽ gồm ba hoặc nhiều thao tác không đồng bộ, theo cách xử lý đúng các lỗi ở mỗi bước và sẽ dễ đọc đối với bất kỳ ai khác hoặc thậm chí với bạn sau vài tuần. Có thể, nhưng rất dễ dàng để tiếp tục lồng các cuộc gọi lại đó và mất dấu vết trên đường đi.
Bây giờ, hãy tạm gác API Facebook sang một bên và chỉ xem xét API Lời hứa góc cạnh, như được triển khai bởi $q
dịch vụ. Mẫu được triển khai bởi dịch vụ này là một nỗ lực để biến lập trình không đồng bộ trở lại thành một thứ giống như một chuỗi các câu lệnh đơn giản tuyến tính, với khả năng 'ném' một lỗi ở bất kỳ bước nào và xử lý nó ở cuối, tương tự về mặt ngữ nghĩa với try/catch
khối quen thuộc .
Hãy xem xét ví dụ giả định này. Giả sử chúng ta có hai hàm, trong đó hàm thứ hai tiêu thụ kết quả của hàm thứ nhất:
var firstFn = function(param) {
// do something with param
return 'firstResult';
};
var secondFn = function(param) {
// do something with param
return 'secondResult';
};
secondFn(firstFn());
Bây giờ hãy tưởng tượng rằng FirstFn và secondFn đều mất nhiều thời gian để hoàn thành, vì vậy chúng tôi muốn xử lý chuỗi này không đồng bộ. Đầu tiên chúng ta tạo một deferred
đối tượng mới , đại diện cho một chuỗi các hoạt động:
var deferred = $q.defer();
var promise = deferred.promise;
Tài promise
sản đại diện cho kết quả cuối cùng của chuỗi. Nếu bạn đăng nhập một lời hứa ngay sau khi tạo nó, bạn sẽ thấy rằng đó chỉ là một đối tượng trống ( {}
). Không có gì để xem, di chuyển ngay dọc.
Cho đến nay, lời hứa của chúng tôi chỉ đại diện cho điểm khởi đầu trong chuỗi. Bây giờ hãy thêm hai hoạt động của chúng tôi:
promise = promise.then(firstFn).then(secondFn);
Các then
phương pháp thêm một bước để chuỗi và sau đó trả về một lời hứa mới đại diện cho kết quả cuối cùng của chuỗi mở rộng. Bạn có thể thêm bao nhiêu bước tùy thích.
Cho đến nay, chúng tôi đã thiết lập chuỗi chức năng của mình, nhưng không có gì thực sự xảy ra. Bạn bắt đầu mọi thứ bằng cách gọi deferred.resolve
, chỉ định giá trị ban đầu bạn muốn chuyển đến bước thực tế đầu tiên trong chuỗi:
deferred.resolve('initial value');
Và rồi ... vẫn không có gì xảy ra. Để đảm bảo rằng các thay đổi mô hình được quan sát đúng cách, Angular không thực sự gọi bước đầu tiên trong chuỗi cho đến lần tiếp theo $apply
được gọi:
deferred.resolve('initial value');
$rootScope.$apply();
// or
$rootScope.$apply(function() {
deferred.resolve('initial value');
});
Vậy xử lý lỗi thì sao? Cho đến nay chúng tôi chỉ chỉ định một trình xử lý thành công ở mỗi bước trong chuỗi. then
cũng chấp nhận một trình xử lý lỗi như một đối số thứ hai tùy chọn. Đây là một ví dụ khác, dài hơn về chuỗi lời hứa, lần này có xử lý lỗi:
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);
Như bạn có thể thấy trong ví dụ này, mỗi trình xử lý trong chuỗi có cơ hội chuyển hướng lưu lượng truy cập sang trình xử lý lỗi tiếp theo thay vì trình xử lý thành công tiếp theo . Trong hầu hết các trường hợp, bạn có thể có một trình xử lý lỗi duy nhất ở cuối chuỗi, nhưng bạn cũng có thể có các trình xử lý lỗi trung gian cố gắng phục hồi.
Để nhanh chóng quay lại ví dụ của bạn (và câu hỏi của bạn), tôi sẽ chỉ nói rằng chúng đại diện cho hai cách khác nhau để điều chỉnh API hướng gọi lại của Facebook theo cách quan sát các thay đổi mô hình của Angular. Ví dụ đầu tiên kết thúc lệnh gọi API trong một lời hứa, có thể được thêm vào một phạm vi và được hiểu bởi hệ thống tạo khuôn mẫu của Angular. Cách thứ hai sử dụng cách tiếp cận mạnh mẽ hơn để đặt kết quả gọi lại trực tiếp trên phạm vi, sau đó gọi $scope.$digest()
để làm cho Angular nhận thức được sự thay đổi từ nguồn bên ngoài.
Hai ví dụ không thể so sánh trực tiếp, vì lần đầu tiên thiếu bước đăng nhập. Tuy nhiên, nói chung, mong muốn đóng gói các tương tác với các API bên ngoài như thế này trong các dịch vụ riêng biệt và cung cấp kết quả cho các bộ điều khiển như đã hứa. Bằng cách đó, bạn có thể tách bộ điều khiển của mình khỏi các mối quan tâm bên ngoài và kiểm tra chúng dễ dàng hơn với các dịch vụ giả.