2013-05-04 43 views
26

Tôi gặp vấn đề trong việc hiểu tại sao những lời từ chối không được truyền qua chuỗi lời hứa và tôi hy vọng một người nào đó có thể giúp tôi hiểu tại sao. Với tôi, việc gắn chức năng vào một chuỗi các lời hứa ngụ ý một ý định rằng tôi phụ thuộc vào một lời hứa ban đầu sẽ được đáp ứng. Thật khó giải thích, vì vậy hãy để tôi hiển thị một ví dụ về vấn đề của tôi trước tiên. (Lưu ý:. Ví dụ này là sử dụng Node và các mô-đun nút chậm Tôi thử nghiệm này với Dojo 1.8.3 và đã có kết quả tương tự)Các lời hứa được giữ nguyên không bị từ chối

var d = require("deferred"); 

var d1 = d(); 

var promise1 = d1.promise.then(
    function(wins) { console.log('promise1 resolved'); return wins;}, 
    function(err) { console.log('promise1 rejected'); return err;}); 
var promise2 = promise1.then(
    function(wins) { console.log('promise2 resolved'); return wins;}, 
    function(err) { console.log('promise2 rejected'); return err;}); 
var promise3 = promise2.then(
    function(wins) { console.log('promise3 resolved'); return wins;}, 
    function(err) { console.log('promise3 rejected'); return err;}); 
d1.reject(new Error()); 

Kết quả chạy hoạt động này là sản lượng này:

promise1 rejected 
promise2 resolved 
promise3 resolved 

Được rồi, với tôi, kết quả này không có ý nghĩa. Bằng cách gắn vào chuỗi lời hứa này, mỗi chuỗi sẽ ngụ ý ý định rằng nó sẽ phụ thuộc vào độ phân giải thành công của d1 và kết quả được truyền xuống chuỗi. Nếu lời hứa trong lời hứa1 không nhận được giá trị thắng, nhưng thay vào đó nhận được một giá trị err trong trình xử lý lỗi của nó, thì lời hứa tiếp theo trong chuỗi có chức năng thành công của nó như thế nào? Không có cách nào nó có thể vượt qua trên một giá trị có ý nghĩa cho lời hứa tiếp theo bởi vì nó đã không nhận được một giá trị riêng của mình.

Một cách khác tôi có thể mô tả những gì tôi đang nghĩ là: Có ba người, John, Ginger và Bob. John sở hữu một cửa hàng tiện ích. Gừng đi vào cửa hàng của anh ta và yêu cầu một túi các vật dụng có màu sắc khác nhau. Anh ta không có chúng trong kho, vì vậy anh ta sẽ gửi yêu cầu tới nhà phân phối của anh ấy để đưa chúng tới anh ta. Trong thời gian đó, anh ta đưa cho Ginger một kiểm tra mưa cho biết anh ta nợ cô ấy túi đồ vật dụng. Bob phát hiện ra Ginger đang nhận được các vật dụng và yêu cầu anh ta nhận được các widget màu xanh khi cô ấy thực hiện với họ. Cô ấy đồng ý và đưa cho anh ta một ghi chú cho biết cô ấy sẽ làm thế. Bây giờ, nhà phân phối của John không thể tìm thấy bất kỳ vật dụng nào trong nguồn cung cấp của họ và nhà sản xuất không làm cho chúng thêm nữa, vì vậy họ thông báo cho John, người mà lần lượt thông báo cho Ginger rằng cô ấy không thể nhận được các vật dụng. Làm thế nào để Bob có thể nhận được một widget màu xanh từ Ginger khi không nhận được bất kỳ chính mình?

Quan điểm thực tế thứ ba mà tôi có về vấn đề này là vấn đề này. Giả sử tôi có hai giá trị tôi muốn cập nhật vào cơ sở dữ liệu. Một là phụ thuộc vào id của khác, nhưng tôi không thể có được id cho đến khi tôi đã chèn nó vào một cơ sở dữ liệu và thu được kết quả. Ngày đầu đó, chèn đầu tiên phụ thuộc vào một truy vấn từ cơ sở dữ liệu. Các cuộc gọi cơ sở dữ liệu trả về các lời hứa mà tôi sử dụng để kết nối hai cuộc gọi thành một chuỗi.

var promise = db.query({parent_id: value}); 
promise.then(function(query_result) { 
    var first_value = { 
     parent_id: query_result[0].parent_id 
    } 
    var promise = db.put(first_value); 
    promise.then(function(first_value_result) { 
     var second_value = { 
      reference_to_first_value_id: first_value_result.id 
     } 
     var promise = db.put(second_value); 
     promise.then(function(second_value_result) { 
      values_successfully_entered(); 
     }, function(err) { return err }); 
    }, function(err) { return err }); 
}, function(err) { return err }); 

Hiện tại, nếu db.query không thành công, hàm này sẽ gọi hàm err của đầu tiên sau đó. Nhưng sau đó nó sẽ gọi chức năng thành công của lời hứa tiếp theo. Trong khi lời hứa đó đang chờ đợi kết quả của giá trị đầu tiên, thay vào đó nó sẽ nhận được thông báo lỗi từ hàm xử lý lỗi của nó.

Vì vậy, câu hỏi của tôi là, tại sao tôi có chức năng chuyển lỗi nếu tôi phải kiểm tra lỗi trong hàm thành công của mình?

Xin lỗi vì thời lượng này. Tôi chỉ không biết cách giải thích nó theo cách khác.

UPDATE và chỉnh

(Lưu ý:.. Tôi đã gỡ bỏ một phản ứng tôi đã từng thực hiện để một số ý kiến ​​Vì vậy, nếu có ai nhận xét về phản ứng của tôi, ý kiến ​​của họ có vẻ nằm ngoài bối cảnh bây giờ mà tôi loại bỏ nó Xin lỗi vì điều này, tôi đang cố gắng giữ điều này càng ngắn càng tốt.)

Cảm ơn mọi người đã trả lời. Tôi muốn xin lỗi tất cả mọi người vì đã viết ra câu hỏi của tôi quá tệ, đặc biệt là mã giả của tôi. Tôi hơi quá hung hăng trong việc cố gắng giữ nó ngắn gọn.

Nhờ phản hồi của Bergi, tôi nghĩ rằng tôi đã tìm thấy lỗi trong logic của mình. Tôi nghĩ rằng tôi có thể đã bỏ qua một vấn đề khác đã gây ra vấn đề tôi đang gặp phải. Điều này có thể khiến chuỗi lời hứa hoạt động khác với tôi nghĩ. Tôi vẫn đang thử nghiệm các yếu tố khác nhau của mã của tôi, vì vậy tôi thậm chí không thể hình thành một câu hỏi thích hợp để xem những gì tôi đang làm sai. Tôi đã muốn cập nhật tất cả các bạn mặc dù và cảm ơn bạn đã giúp đỡ của bạn.

+1

Bạn đang sử dụng phiên bản nào? Điều này hiển thị 'bị từ chối' 3 lần đối với tôi vào 0.10.0 và bị hoãn 0.6.3. – loganfsmyth

+0

Nó hoạt động cho tôi cũng như trên nút 0.8.3 và hoãn 0.6.3 https://gist.github.com/Stuk/694b2377057453aa6946 –

Trả lời

25

Với tôi, kết quả này không có ý nghĩa. Bằng cách gắn vào chuỗi lời hứa này, mỗi chuỗi sẽ ngụ ý ý định rằng nó sẽ phụ thuộc vào độ phân giải thành công của d1 và kết quả được chuyển xuống chuỗi

No. Bạn mô tả không phải là một chuỗi, nhưng chỉ cần gắn tất cả các cuộc gọi lại đến d1. Tuy nhiên, nếu bạn muốn gắn kết thứ gì đó với then, kết quả cho promise2 phụ thuộc vào độ phân giải promise1và cách các cuộc gọi lại then xử lý số.

Các tài liệu nhà nước:

Trả về một lời hứa mới cho kết quả của callback (s).

Phương pháp .then thường được xem xét theo điều khoản của Promises/A specification (hoặc thậm chí chặt chẽ hơn Promsises/A+ one). Điều đó có nghĩa là các callbacks shell return hứa hẹn sẽ được đồng hóa để trở thành độ phân giải promise2 và nếu không có xử lý thành công/lỗi, kết quả tương ứng sẽ được chuyển trực tiếp đến promise2 - vì vậy bạn có thể chỉ cần bỏ qua trình xử lý để truyền lỗi.

Tuy nhiên, nếu lỗi là được xử lý, kết quả là promise2 được xem là cố định và sẽ được đáp ứng với giá trị đó. Nếu bạn không muốn điều đó, bạn sẽ phải re- throw lỗi, giống như trong mệnh đề try-catch. Ngoài ra, bạn có thể trả lại lời hứa từ chối (để được) từ trình xử lý. Không chắc chắn những gì Dojo cách để từ chối, nhưng:

var d1 = d(); 

var promise1 = d1.promise.then(
    function(wins) { console.log('promise1 resolved'); return wins;}, 
    function(err) { console.log('promise1 rejected'); throw err;}); 
var promise2 = promise1.then(
    function(wins) { console.log('promise2 resolved'); return wins;}, 
    function(err) { console.log('promise2 rejected'); throw err;}); 
var promise3 = promise2.then(
    function(wins) { console.log('promise3 resolved'); return wins;}, 
    function(err) { console.log('promise3 rejected'); throw err;}); 
d1.reject(new Error()); 

thế nào là Bob có thể nhận được một widget xanh từ Ginger khi đã không nhận được bất kỳ bản thân mình?

Anh ấy không thể thực hiện được. Nếu không có trình xử lý lỗi, anh ta sẽ chỉ nhận được thông báo (((từ nhà phân phối) từ John) từ Ginger) rằng không còn tiện ích con nào. Tuy nhiên, nếu Ginger thiết lập một trình xử lý lỗi cho trường hợp đó, cô vẫn có thể thực hiện lời hứa của mình để cung cấp cho Bob một tiện ích bằng cách cho anh ta một cái màu xanh lá cây từ kho của chính mình nếu không có màu xanh nào còn lại ở John hoặc nhà phân phối của anh ta.

Để dịch lỗi gọi lại của bạn thành metapher, return err từ trình xử lý sẽ chỉ giống như nói "nếu không còn tiện ích nào, chỉ cần lưu ý rằng không còn thứ gì - nó tốt như tiện ích mong muốn" .

Trong trường hợp cơ sở dữ liệu, nếu db.truy vấn không thành công, nó sẽ gọi hàm err của đầu tiên sau đó

... điều đó có nghĩa là lỗi được xử lý ở đó. Nếu bạn không làm điều đó, chỉ cần bỏ qua gọi lại lỗi. Btw, callbacks thành công của bạn không phải là return những lời hứa mà họ đang tạo ra, vì vậy chúng dường như khá vô dụng. Đúng sẽ là:

var promise = db.query({parent_id: value}); 
promise.then(function(query_result) { 
    var first_value = { 
     parent_id: query_result[0].parent_id 
    } 
    var promise = db.put(first_value); 
    return promise.then(function(first_value_result) { 
     var second_value = { 
      reference_to_first_value_id: first_value_result.id 
     } 
     var promise = db.put(second_value); 
     return promise.then(function(second_value_result) { 
      return values_successfully_entered(); 
     }); 
    }); 
}); 

hay, kể từ khi bạn không cần đóng cửa các giá trị kết quả truy cập từ callbacks trước, thậm chí:

db.query({parent_id: value}).then(function(query_result) { 
    return db.put({ 
     parent_id: query_result[0].parent_id 
    }); 
}).then(function(first_value_result) { 
    return db.put({ 
     reference_to_first_value_id: first_value_result.id 
    }); 
}.then(values_successfully_entered); 
+3

Khi sử dụng angularJS với $ q, từ khóa ném là để thay thế bằng $ q.reject (err). – Toilal

+3

Để làm sạch nhận xét của @ Toilal, thay thế * ưa thích * thành 'throw', là' trả về $ q.reject (err) '. 'tôi sẽ ném', tôi tin rằng, vẫn còn hoạt động; nó chỉ chậm hơn nhiều. – Malvolio

1

@Jordan trước hết như bình luận lưu ý, khi sử dụng lib hoãn, ví dụ đầu tiên của bạn chắc chắn tạo ra kết quả bạn mong đợi:

promise1 rejected 
promise2 rejected 
promise3 rejected 

Thứ hai, ngay cả khi nó tạo ra kết quả bạn đề xuất, nó sẽ không ảnh hưởng đến luồng thực thi đoạn thứ hai của bạn sa cắn khác nhau, giống như:

promise.then(function(first_value) { 
    console.log('promise1 resolved'); 
    var promise = db.put(first_value); 
    promise.then(function (second_value) { 
     console.log('promise2 resolved'); 
     var promise = db.put(second_value); 
     promise.then(
      function (wins) { console.log('promise3 resolved'); }, 
      function (err) { console.log('promise3 rejected'); return err; }); 
    }, function (err) { console.log('promise2 rejected'); return err;}); 
}, function (err) { console.log('promise1 rejected'); return err}); 

và rằng, trong trường hợp của lời hứa đầu tiên bị từ chối sẽ chỉ ra:

promise1 rejected 

Tuy nhiên (nhận đến phần thú vị nhất) mặc dù thư viện thu nhập hoãn lại chắc chắn trả về 3 x rejected, hầu hết các thư viện hứa hẹn khác sẽ trả lại 1 x rejected, 2 x resolved (dẫn đến giả định bạn nhận được những kết quả đó bằng cách sử dụng một số thư viện lời hứa khác thay thế).

Điều gì gây nhầm lẫn hơn nữa, các thư viện khác chính xác hơn với hành vi của chúng. Hãy để tôi giải thích.

Trong đối tác thế giới đồng bộ hóa "từ chối lời hứa" là throw. Vì vậy, theo ngữ nghĩa, async deferred.reject(new Error()) đồng bộ tương đương với throw new Error(). Trong ví dụ của bạn, bạn không ném lỗi trong các cuộc gọi lại đồng bộ hóa của mình, bạn chỉ trả lại chúng, do đó bạn chuyển sang luồng thành công, với lỗi là giá trị thành công. Để đảm bảo từ chối được chuyển tiếp, bạn cần phải ném lại các lỗi của mình:

function (err) { console.log('promise1 rejected'); throw err; }); 

Vì vậy, bây giờ câu hỏi là, tại sao thư viện hoãn lại đã trả về lỗi khi từ chối?

Lý do cho điều đó, là việc từ chối trong các công việc trì hoãn có đôi chút khác biệt. Trong quy tắc lib bị trì hoãn, quy tắc là: lời hứa bị từ chối khi được giải quyết với trường hợp lỗi, vì vậy ngay cả khi bạn thực hiện deferred.resolve(new Error()), nó sẽ hoạt động như deferred.reject(new Error()) và nếu bạn cố gắng thực hiện deferred.reject(notAnError). bị từ chối chỉ với trường hợp lỗi. Điều đó làm rõ lý do tại sao lỗi được trả về từ số gọi lại then từ chối lời hứa.

Có một số lý do hợp lệ đằng sau logic hoãn lại, nhưng vẫn không ngang bằng cách throw hoạt động trong JavaScript và do hành vi này được lên lịch thay đổi với phiên bản v0.7 bị trì hoãn.

tóm tắt ngắn:

Để tránh nhầm lẫn và kết quả bất ngờ chỉ cần làm theo các quy tắc thực hành tốt:

  1. Luôn từ chối những lời hứa của bạn với một trường hợp lỗi (theo quy tắc của thế giới đồng bộ, nơi ném giá trị đó không phải là một lỗi được coi là một thực tế xấu).
  2. Từ chối cuộc gọi lại đồng bộ hóa bằng cách ném lỗi (trả lại chúng không đảm bảo từ chối).

Tuân thủ ở trên, bạn sẽ nhận được cả kết quả phù hợp và mong đợi trong cả thư viện lời hứa phổ biến lẫn thư viện phổ biến khác.

0

Sử dụng có thể bao bọc lỗi ở mỗi cấp độ của Lời hứa. Tôi xích các sai sót trong TraceError:

class TraceError extends Error { 
    constructor(message, ...causes) { 
    super(message); 

    const stack = Object.getOwnPropertyDescriptor(this, 'stack'); 

    Object.defineProperty(this, 'stack', { 
     get:() => { 
     const stacktrace = stack.get.call(this); 
     let causeStacktrace = ''; 

     for (const cause of causes) { 
      if (cause.sourceStack) { // trigger lookup 
      causeStacktrace += `\n${cause.sourceStack}`; 
      } else if (cause instanceof Error) { 
      causeStacktrace += `\n${cause.stack}`; 
      } else { 
      try { 
       const json = JSON.stringify(cause, null, 2); 
       causeStacktrace += `\n${json.split('\n').join('\n ')}`; 
      } catch (e) { 
       causeStacktrace += `\n${cause}`; 
       // ignore 
      } 
      } 
     } 

     causeStacktrace = causeStacktrace.split('\n').join('\n '); 

     return stacktrace + causeStacktrace; 
     } 
    }); 

    // access first error 
    Object.defineProperty(this, 'cause', {value:() => causes[0], enumerable: false, writable: false}); 

    // untested; access cause stack with error.causes() 
    Object.defineProperty(this, 'causes', {value:() => causes, enumerable: false, writable: false}); 
    } 
} 

Cách sử dụng

throw new TraceError('Could not set status', srcError, ...otherErrors); 

Output

Chức năng

TraceError#cause - first error 
TraceError#causes - list of chained errors 
Các vấn đề liên quan