Lời hứa JavaScript - từ chối so với ném


384

Tôi đã đọc một số bài viết về chủ đề này, nhưng vẫn chưa rõ ràng nếu có sự khác biệt giữa Promise.rejectso với ném lỗi. Ví dụ,

Sử dụng Promise.reject

return asyncIsPermitted()
    .then(function(result) {
        if (result === true) {
            return true;
        }
        else {
            return Promise.reject(new PermissionDenied());
        }
    });

Sử dụng ném

return asyncIsPermitted()
    .then(function(result) {
        if (result === true) {
            return true;
        }
        else {
            throw new PermissionDenied();
        }
    });

Sở thích của tôi là sử dụng throwđơn giản vì nó ngắn hơn, nhưng tự hỏi liệu có bất kỳ lợi thế nào so với cái kia không.


9
Cả hai phương pháp tạo ra phản ứng chính xác như nhau. Người .then()xử lý nắm bắt ngoại lệ ném và tự động biến nó thành một lời hứa bị từ chối. Vì tôi đã đọc rằng các ngoại lệ bị ném không đặc biệt nhanh để thực thi, tôi đoán rằng việc trả lại lời hứa bị từ chối có thể nhanh hơn một chút để thực hiện, nhưng bạn phải đưa ra một thử nghiệm trong nhiều trình duyệt hiện đại nếu điều đó quan trọng cần biết. Cá nhân tôi sử dụng throwvì tôi thích sự dễ đọc.
jfriend00

@webduvet không phải với Lời hứa - chúng được thiết kế để hoạt động với ném.
tham gia

15
Một nhược điểm throwlà nó sẽ không dẫn đến một lời hứa bị từ chối nếu nó bị ném từ trong một cuộc gọi lại không đồng bộ, chẳng hạn như setTimeout. jsfiddle.net/m07van33 @Blondie câu trả lời của bạn đã đúng.
Kevin B

@joews không có nghĩa là nó tốt;)
webduvet

1
À, đúng rồi. Vì vậy, một lời giải thích cho nhận xét của tôi sẽ là, "nếu nó được ném từ bên trong một cuộc gọi lại không đồng bộ không được quảng cáo " . Tôi biết có một ngoại lệ cho điều đó, tôi chỉ không thể nhớ nó là gì. Tôi cũng thích sử dụng throw đơn giản vì tôi thấy nó dễ đọc hơn và cho phép tôi bỏ qua rejectnó khỏi danh sách param của tôi.
Kevin B

Câu trả lời:


345

Không có lợi thế của việc sử dụng cái này so với cái kia, nhưng, có một trường hợp cụ thể throwkhông hoạt động. Tuy nhiên, những trường hợp có thể được sửa chữa.

Bất cứ khi nào bạn ở trong một cuộc gọi lại lời hứa, bạn có thể sử dụng throw. Tuy nhiên, nếu bạn đang trong bất kỳ cuộc gọi lại không đồng bộ nào khác, bạn phải sử dụng reject.

Ví dụ: điều này sẽ không kích hoạt việc bắt:

new Promise(function() {
  setTimeout(function() {
    throw 'or nah';
    // return Promise.reject('or nah'); also won't work
  }, 1000);
}).catch(function(e) {
  console.log(e); // doesn't happen
});

Thay vào đó, bạn để lại một lời hứa chưa được giải quyết và một ngoại lệ chưa được giải quyết. Đó là một trường hợp mà bạn muốn sử dụng thay thế reject. Tuy nhiên, bạn có thể khắc phục điều này theo hai cách.

  1. bằng cách sử dụng chức năng từ chối ban đầu của Promise trong thời gian chờ:

new Promise(function(resolve, reject) {
  setTimeout(function() {
    reject('or nah');
  }, 1000);
}).catch(function(e) {
  console.log(e); // works!
});

  1. bằng cách hứa hẹn thời gian chờ:

function timeout(duration) { // Thanks joews
  return new Promise(function(resolve) {
    setTimeout(resolve, duration);
  });
}

timeout(1000).then(function() {
  throw 'worky!';
  // return Promise.reject('worky'); also works
}).catch(function(e) {
  console.log(e); // 'worky!'
});


54
Đáng nói là các vị trí bên trong một cuộc gọi lại không đồng bộ không được quảng cáo mà bạn không thể sử dụng throw error, bạn cũng không thể sử dụng return Promise.reject(err)đó là những gì OP yêu cầu chúng tôi so sánh. Về cơ bản, đây là lý do tại sao bạn không nên đặt các cuộc gọi lại không đồng bộ bên trong các lời hứa. Hứa hẹn mọi thứ không đồng bộ và sau đó bạn không có những hạn chế này.
jfriend00

9
"Tuy nhiên, nếu bạn đang ở bất kỳ loại gọi lại nào khác" thực sự nên là "Tuy nhiên, nếu bạn đang ở bất kỳ loại gọi lại không đồng bộ nào khác ". Các cuộc gọi lại có thể đồng bộ (ví dụ với Array#forEach) và với những cuộc gọi đó, việc ném vào bên trong chúng sẽ hoạt động.
Félix Saparelli

2
@KevinB đọc những dòng này "có một trường hợp cụ thể trong đó ném sẽ không hoạt động." và "Bất cứ khi nào bạn ở trong một cuộc gọi lại lời hứa, bạn có thể sử dụng cú ném. Tuy nhiên, nếu bạn đang trong bất kỳ cuộc gọi lại không đồng bộ nào khác, bạn phải sử dụng từ chối." Tôi có cảm giác rằng các đoạn ví dụ sẽ hiển thị các trường hợp throwkhông hoạt động và thay vào đó Promise.rejectlà một lựa chọn tốt hơn. Tuy nhiên, đoạn trích không bị ảnh hưởng với bất kỳ hai lựa chọn nào và cho kết quả giống nhau bất kể bạn chọn gì. Tui bỏ lỡ điều gì vậy?
Anshul

2
Đúng. nếu bạn sử dụng throw in setTimeout, thì lệnh bắt sẽ không được gọi. bạn phải sử dụng rejectcái đã được chuyển cho cuộc new Promise(fn)gọi lại.
Kevin B

2
@KevinB cảm ơn vì đã ở cùng. Ví dụ được đưa ra bởi OP đề cập ông đặc biệt muốn so sánh return Promise.reject()throw. Anh ta không đề cập đến cuộc rejectgọi lại được đưa ra trong new Promise(function(resolve, reject))xây dựng. Vì vậy, trong khi hai đoạn mã của bạn thể hiện chính xác khi nào bạn nên sử dụng cuộc gọi lại giải quyết, câu hỏi của OP không phải là điều đó.
Anshul

201

Một thực tế quan trọng khác là reject() KHÔNG chấm dứt luồng kiểm soát như một returntuyên bố. Ngược lại throwkhông chấm dứt dòng điều khiển.

Thí dụ:

new Promise((resolve, reject) => {
  throw "err";
  console.log("NEVER REACHED");
})
.then(() => console.log("RESOLVED"))
.catch(() => console.log("REJECTED"));

đấu với

new Promise((resolve, reject) => {
  reject(); // resolve() behaves similarly
  console.log("ALWAYS REACHED"); // "REJECTED" will print AFTER this
})
.then(() => console.log("RESOLVED"))
.catch(() => console.log("REJECTED"));


51
Vâng, quan điểm là chính xác nhưng so sánh là khó khăn. Bởi vì thông thường bạn nên trả lại lời hứa bị từ chối bằng cách viết return reject(), vì vậy dòng tiếp theo sẽ không chạy.
AZ.

7
Tại sao bạn muốn trả lại nó?
lukyer

31
Trong trường hợp này, return reject()chỉ đơn giản là một tốc ký cho reject(); returntức là những gì bạn muốn là chấm dứt dòng chảy. Giá trị trả về của hàm thực thi (hàm được truyền tới new Promise) không được sử dụng, vì vậy điều này là an toàn.
Félix Saparelli

47

Có, sự khác biệt lớn nhất là từ chối là chức năng gọi lại được thực hiện sau khi lời hứa bị từ chối, trong khi ném không thể được sử dụng không đồng bộ. Nếu bạn chọn sử dụng từ chối, mã của bạn sẽ tiếp tục chạy bình thường theo kiểu không đồng bộ trong khi throw sẽ ưu tiên hoàn thành chức năng trình phân giải (chức năng này sẽ chạy ngay lập tức).

Một ví dụ tôi đã thấy giúp làm rõ vấn đề với tôi là bạn có thể đặt chức năng Hết giờ với từ chối, ví dụ:

new Promise(_, reject) {
 setTimeout(reject, 3000);
});

Ở trên không thể viết bằng ném.

Trong ví dụ nhỏ của bạn, sự khác biệt không thể phân biệt nhưng khi xử lý khái niệm không đồng bộ phức tạp hơn, sự khác biệt giữa hai có thể rất lớn.


1
Điều này nghe có vẻ như là một khái niệm quan trọng, nhưng tôi không hiểu nó là bằng văn bản. Vẫn còn quá mới với Lời hứa, tôi đoán vậy.
David Spector

43

TLDR: Một chức năng khó sử dụng khi đôi khi trả lại lời hứa và đôi khi ném ngoại lệ. Khi viết chức năng không đồng bộ, thích báo hiệu thất bại bằng cách trả lại lời hứa bị từ chối

Ví dụ cụ thể của bạn che giấu một số khác biệt quan trọng giữa chúng:

Vì bạn đang xử lý lỗi trong chuỗi lời hứa, các ngoại lệ được ném sẽ tự động được chuyển đổi thành lời hứa bị từ chối. Điều này có thể giải thích tại sao chúng dường như có thể hoán đổi cho nhau - chúng không như vậy.

Hãy xem xét tình huống dưới đây:

checkCredentials = () => {
    let idToken = localStorage.getItem('some token');
    if ( idToken ) {
      return fetch(`https://someValidateEndpoint`, {
        headers: {
          Authorization: `Bearer ${idToken}`
        }
      })
    } else {
      throw new Error('No Token Found In Local Storage')
    }
  }

Đây sẽ là một mô hình chống vì bạn sẽ cần hỗ trợ cả các trường hợp lỗi không đồng bộ và đồng bộ hóa. Nó có thể trông giống như:

try {
  function onFulfilled() { ... do the rest of your logic }
  function onRejected() { // handle async failure - like network timeout }
  checkCredentials(x).then(onFulfilled, onRejected);
} catch (e) {
  // Error('No Token Found In Local Storage')
  // handle synchronous failure
} 

Không tốt và đây là chính xác nơi Promise.reject(có sẵn trong phạm vi toàn cầu) đến để giải cứu và phân biệt chính nó một cách hiệu quả throw. Bộ tái cấu trúc bây giờ trở thành:

checkCredentials = () => {
  let idToken = localStorage.getItem('some_token');
  if (!idToken) {
    return Promise.reject('No Token Found In Local Storage')
  }
  return fetch(`https://someValidateEndpoint`, {
    headers: {
      Authorization: `Bearer ${idToken}`
    }
  })
}

Điều này hiện cho phép bạn sử dụng chỉ một catch()lỗi cho lỗi mạng kiểm tra lỗi đồng bộ về việc thiếu mã thông báo:

checkCredentials()
      .catch((error) => if ( error == 'No Token' ) {
      // do no token modal
      } else if ( error === 400 ) {
      // do not authorized modal. etc.
      }

1
Tuy nhiên, ví dụ của Op luôn trả lại một lời hứa. Câu hỏi đề cập đến việc bạn nên sử dụng Promise.rejecthay throwkhi nào bạn muốn trả lại một lời hứa bị từ chối (một lời hứa sẽ chuyển sang tiếp theo .catch()).
Marcos Pereira

@maxwell - Tôi thích bạn ví dụ. Đồng thời, nếu tìm nạp, bạn sẽ thêm một lần bắt và trong đó bạn ném ngoại lệ thì bạn sẽ an toàn khi sử dụng thử ... bắt ... Không có thế giới hoàn hảo nào về dòng ngoại lệ, nhưng tôi nghĩ rằng sử dụng một mẫu đơn có ý nghĩa và kết hợp các mẫu không an toàn (căn chỉnh với mẫu tương tự của bạn so với mẫu tương tự chống mẫu).
dùng3053247

1
Câu trả lời tuyệt vời nhưng tôi tìm thấy ở đây một lỗ hổng - mẫu này giả sử tất cả các lỗi được xử lý bằng cách trả lại Promise.reject - điều gì xảy ra với tất cả các lỗi không mong muốn đơn giản có thể được ném ra từ checkCredentials ()?
chenop

1
Vâng bạn đúng @chenop - để bắt những lỗi không mong muốn đó, bạn sẽ cần phải thử / bắt giữ
maxwell

Tôi không hiểu trường hợp của @ maxwell. Bạn không thể cấu trúc nó để bạn làm như vậy checkCredentials(x).then(onFulfilled).catch(e) {}, và có catchxử lý cả trường hợp từ chối và trường hợp lỗi ném?
Ben Wheeler

5

Một ví dụ để thử. Chỉ cần thay đổi isVersionThrow thành false để sử dụng từ chối thay vì ném.

const isVersionThrow = true

class TestClass {
  async testFunction () {
    if (isVersionThrow) {
      console.log('Throw version')
      throw new Error('Fail!')
    } else {
      console.log('Reject version')
      return new Promise((resolve, reject) => {
        reject(new Error('Fail!'))
      })
    }
  }
}

const test = async () => {
  const test = new TestClass()
  try {
    var response = await test.testFunction()
    return response 
  } catch (error) {
    console.log('ERROR RETURNED')
    throw error 
  }  
}

test()
.then(result => {
  console.log('result: ' + result)
})
.catch(error => {
  console.log('error: ' + 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.