Tôi có cần quay lại sau khi giải quyết / từ chối sớm không?


262

Giả sử tôi có đoạn mã sau.

function divide(numerator, denominator) {
 return new Promise((resolve, reject) => {

  if(denominator === 0){
   reject("Cannot divide by 0");
   return; //superfluous?
  }

  resolve(numerator / denominator);

 });
}

Nếu mục đích của tôi là sử dụng rejectđể thoát sớm, tôi có nên tập thói quen returning ngay sau đó không?


5
Có, do sắp hoàn thành

Câu trả lời:


371

Các returnmục đích là để chấm dứt việc thực hiện các chức năng sau khi bị từ chối, và ngăn chặn việc thực hiện các mã sau nó.

function divide(numerator, denominator) {
  return new Promise((resolve, reject) => {

    if (denominator === 0) {
      reject("Cannot divide by 0");
      return; // The function execution ends here 
    }

    resolve(numerator / denominator);
  });
}

Trong trường hợp này, nó ngăn chặn việc resolve(numerator / denominator);thực thi, không thực sự cần thiết. Tuy nhiên, vẫn nên chấm dứt việc thực hiện để ngăn chặn một cái bẫy có thể xảy ra trong tương lai. Ngoài ra, đó là một cách thực hành tốt để ngăn chặn việc chạy mã không cần thiết.

Lý lịch

Một lời hứa có thể ở một trong 3 trạng thái:

  1. đang chờ xử lý - trạng thái ban đầu. Từ chờ xử lý, chúng tôi có thể chuyển sang một trong những tiểu bang khác
  2. hoàn thành - hoạt động thành công
  3. bị từ chối - hoạt động thất bại

Khi một lời hứa được thực hiện hoặc từ chối, nó sẽ ở trong trạng thái này vô thời hạn (giải quyết). Vì vậy, từ chối một lời hứa đã thực hiện hoặc thực hiện một lời hứa bị từ chối, sẽ không có hiệu lực.

Đoạn ví dụ này cho thấy rằng mặc dù lời hứa đã được thực hiện sau khi bị từ chối, nó vẫn bị từ chối.

function divide(numerator, denominator) {
  return new Promise((resolve, reject) => {
    if (denominator === 0) {
      reject("Cannot divide by 0");
    }

    resolve(numerator / denominator);
  });
}

divide(5,0)
  .then((result) => console.log('result: ', result))
  .catch((error) => console.log('error: ', error));

Vậy tại sao chúng ta cần quay trở lại?

Mặc dù chúng ta không thể thay đổi trạng thái lời hứa đã được giải quyết, việc từ chối hoặc giải quyết sẽ không dừng việc thực thi phần còn lại của chức năng. Hàm này có thể chứa mã sẽ tạo ra kết quả khó hiểu. Ví dụ:

function divide(numerator, denominator) {
  return new Promise((resolve, reject) => {
    if (denominator === 0) {
      reject("Cannot divide by 0");
    }
    
    console.log('operation succeeded');

    resolve(numerator / denominator);
  });
}

divide(5, 0)
  .then((result) => console.log('result: ', result))
  .catch((error) => console.log('error: ', error));

Ngay cả khi hàm không chứa mã như vậy ngay bây giờ, điều này sẽ tạo ra một cái bẫy trong tương lai. Một bộ tái cấu trúc trong tương lai có thể bỏ qua thực tế là mã vẫn được thực thi sau khi lời hứa bị từ chối và sẽ khó gỡ lỗi.

Dừng thực thi sau khi giải quyết / từ chối:

Đây là công cụ lưu lượng kiểm soát JS tiêu chuẩn.

  • Quay trở lại sau resolve/ reject:

  • Trả về với resolve/ reject- vì giá trị trả về của cuộc gọi lại bị bỏ qua, chúng tôi có thể lưu một dòng bằng cách trả về câu lệnh từ chối / giải quyết:

  • Sử dụng khối if / other:

Tôi thích sử dụng một trong các returntùy chọn vì mã là phẳng hơn.


28
Đáng lưu ý rằng mã sẽ không thực sự hoạt động khác đi nếu returncó hay không bởi vì một khi trạng thái lời hứa đã được đặt, nó không thể thay đổi nên việc gọi resolve()sau khi gọi reject()sẽ không làm gì cả ngoại trừ sử dụng một vài chu kỳ CPU bổ sung. Tôi, bản thân tôi, sẽ sử dụng returnchỉ từ quan điểm sạch sẽ và hiệu quả của mã, nhưng nó không bắt buộc trong ví dụ cụ thể này.
jfriend00

1
Hãy thử sử dụng Promise.try(() => { })thay vì Promise mới và tránh sử dụng các cuộc gọi giải quyết / từ chối. Thay vào đó, bạn chỉ có thể viết return denominator === 0 ? throw 'Cannot divide by zero' : numerator / denominator; Tôi sử dụng Promise.trynhư một phương tiện để khởi động một Lời hứa cũng như loại bỏ những lời hứa được gói trong các khối thử / bắt có vấn đề.
kingdango

2
Thật tốt khi biết, và tôi thích mẫu này. Tuy nhiên, tại thời điểm này Promise.try là đề xuất giai đoạn 0, vì vậy bạn chỉ có thể sử dụng nó với shim hoặc bằng cách sử dụng thư viện lời hứa như bluebird hoặc Q.
Ori Drori

6
@ jfriend00 Rõ ràng trong ví dụ đơn giản này, mã sẽ không hoạt động khác. Nhưng điều gì sẽ xảy ra nếu bạn có mã sau khi rejectthực hiện một cái gì đó đắt tiền, như kết nối với cơ sở dữ liệu hoặc điểm cuối API? Tất cả sẽ không cần thiết và làm bạn tốn tiền và tài nguyên, đặc biệt là nếu bạn đang kết nối với thứ gì đó như cơ sở dữ liệu AWS hoặc điểm cuối API Gateway. Trong trường hợp đó, bạn chắc chắn sẽ sử dụng trả về để tránh mã không cần thiết được thực thi.
Jake Wilson

3
@JakeWilson - Tất nhiên, đó chỉ là dòng mã bình thường trong Javascript và không liên quan gì đến lời hứa cả. Nếu bạn đã xử lý xong hàm và không muốn có thêm mã nào để thực thi trong đường dẫn mã hiện tại, bạn chèn một return.
jfriend00

37

Một thành ngữ phổ biến, có thể hoặc không phải là tách trà của bạn, là kết hợp returnvới reject, đồng thời từ chối lời hứa và thoát khỏi chức năng, để phần còn lại của chức năng bao gồm cả chức năng resolvekhông được thực thi. Nếu bạn thích phong cách này, nó có thể làm cho mã của bạn nhỏ gọn hơn một chút.

function divide(numerator, denominator) {
  return new Promise((resolve, reject) => {
    if (denominator === 0) return reject("Cannot divide by 0");
                           ^^^^^^^^^^^^^^
    resolve(numerator / denominator);
  });
}

Điều này hoạt động tốt bởi vì hàm tạo Promise không làm gì với bất kỳ giá trị trả về nào, và trong mọi trường hợp resolverejectkhông trả về gì cả.

Thành ngữ tương tự có thể được sử dụng với kiểu gọi lại được hiển thị trong câu trả lời khác:

function divide(nom, denom, cb){
  if(denom === 0) return cb(Error("Cannot divide by zero"));
                  ^^^^^^^^^
  cb(null, nom / denom);
} 

Một lần nữa, điều này hoạt động tốt bởi vì người gọi dividekhông mong đợi nó trả lại bất cứ điều gì và không làm bất cứ điều gì với giá trị trả lại.


6
Tôi không thích điều này. Điều này đưa ra quan niệm rằng bạn đang trả lại một cái gì đó mà thực tế bạn không có. Bạn đang gọi hàm từ chối và sau đó sử dụng return để kết thúc thực thi hàm. Giữ chúng trên các dòng riêng biệt, những gì bạn đang làm sẽ chỉ khiến mọi người nhầm lẫn. Mã dễ đọc là vua.
K - Độc tính trong SO đang tăng lên.

7
@KarlMorrison trên thực tế bạn đang trả lại "một cái gì đó", một lời hứa bị từ chối. Tôi nghĩ rằng "khái niệm" mà bạn đang nói là rất cá nhân. Không có gì sai khi trả lại một rejecttrạng thái
Frondor

5
@Frondor Tôi không nghĩ bạn hiểu những gì tôi đã viết. Tất nhiên bạn và tôi hiểu điều này, không có gì xảy ra khi trả lại một từ chối trên cùng một dòng. Nhưng những gì về các nhà phát triển không quen với JavaScript trong một dự án thì sao? Kiểu lập trình này làm giảm khả năng đọc cho những người như vậy. Hệ sinh thái JavaScript ngày nay là một mớ hỗn độn và mọi người truyền bá loại thực hành này sẽ chỉ làm cho nó tồi tệ hơn. Đây là thực hành xấu.
K - Độc tính trong SO đang tăng lên.

1
@KarlMorrison Ý kiến ​​cá nhân! = Thực hành xấu. Nó có thể sẽ giúp một nhà phát triển Javascript mới hiểu điều gì đang xảy ra với sự trở lại.
Toby Caulk

1
@TobyCaulk Nếu mọi người cần học về lợi nhuận thì họ không nên chơi với Promise, họ nên học lập trình cơ bản.
K - Độc tính trong SO đang tăng lên.

10

Về mặt kỹ thuật không cần thiết ở đây 1 - vì một Lời hứa có thể được giải quyết hoặc từ chối, độc quyền và chỉ một lần. Kết quả Promise đầu tiên sẽ thắng và mọi kết quả tiếp theo đều bị bỏ qua. Điều này khác với các cuộc gọi lại kiểu Node.

Điều đó được nói rằng đó là một thực hành sạch sẽ tốt để đảm bảo rằng chính xác một cái được gọi, khi thực tế, và thực sự trong trường hợp này vì không có quá trình xử lý không đồng bộ / trả chậm. Quyết định "trở về sớm" không khác gì kết thúc bất kỳ chức năng nào khi công việc của nó hoàn tất - so với việc tiếp tục xử lý không liên quan hoặc không cần thiết.

Trở lại vào thời điểm thích hợp (hoặc sử dụng các điều kiện khác để tránh thực hiện trường hợp "khác") làm giảm khả năng vô tình chạy mã trong trạng thái không hợp lệ hoặc thực hiện các tác dụng phụ không mong muốn; và do đó, nó làm cho mã ít bị "phá vỡ bất ngờ".


1 Câu trả lời về mặt kỹ thuật này cũng xoay quanh thực tế là trong trường hợp này , mã sau khi "trả lại", nếu nó bị bỏ qua, sẽ không dẫn đến tác dụng phụ. JavaScript sẽ vui vẻ chia cho số 0 và trả về + Infinity / -Infinity hoặc NaN.


Chú thích đẹp !!
HankCa

9

Nếu bạn không "quay lại" sau khi giải quyết / từ chối, những điều tồi tệ (như chuyển hướng trang) có thể xảy ra sau khi bạn muốn nó dừng lại. Nguồn: Tôi chạy vào đây.


6
+1 cho ví dụ. Tôi gặp sự cố khi chương trình của tôi thực hiện hơn 100 truy vấn cơ sở dữ liệu không hợp lệ và tôi không thể hiểu tại sao. Hóa ra tôi đã không "trở về" sau khi bị từ chối. Đó là một lỗi nhỏ nhưng tôi đã học được bài học của mình.
AdamInTheOculus

8

Câu trả lời của Ori đã giải thích rằng nó không cần thiết returnnhưng đó là một thực hành tốt. Lưu ý rằng hàm tạo lời hứa được ném an toàn, vì vậy nó sẽ bỏ qua các ngoại lệ được ném sau đó trong đường dẫn, về cơ bản bạn có các tác dụng phụ mà bạn không thể dễ dàng quan sát được.

Lưu ý rằng returning sớm cũng rất phổ biến trong các cuộc gọi lại:

function divide(nom, denom, cb){
     if(denom === 0){
         cb(Error("Cannot divide by zero");
         return; // unlike with promises, missing the return here is a mistake
     }
     cb(null, nom / denom); // this will divide by zero. Since it's a callback.
} 

Vì vậy, trong khi đó là thông lệ tốt trong các lời hứa, nó được yêu cầu với các cuộc gọi lại. Một số lưu ý về mã của bạn:

  • Trường hợp sử dụng của bạn là giả thuyết, không thực sự sử dụng lời hứa với các hành động đồng bộ.
  • Hàm tạo hứa hẹn bỏ qua các giá trị trả về. Một số thư viện sẽ cảnh báo nếu bạn trả về giá trị không xác định để cảnh báo bạn trước sai lầm khi quay lại đó. Hầu hết không thông minh.
  • Trình xây dựng lời hứa được ném an toàn, nó sẽ chuyển đổi các ngoại lệ thành các từ chối nhưng như những người khác đã chỉ ra - một lời hứa sẽ giải quyết một lần.

4

Trong nhiều trường hợp, có thể xác thực các tham số một cách riêng biệt và ngay lập tức trả lại lời hứa bị từ chối với Promise.reject (lý do) .

function divide2(numerator, denominator) {
  if (denominator === 0) {
    return Promise.reject("Cannot divide by 0");
  }
  
  return new Promise((resolve, reject) => {
    resolve(numerator / denominator);
  });
}


divide2(4, 0).then((result) => console.log(result), (error) => console.log(error));

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.