JavaScript承诺 - 拒绝与抛出

Nar*_*esh 329 javascript promise

我已经阅读了几篇关于这个主题的文章,但我仍然不清楚是否Promise.reject与抛出错误之间存在差异.例如,

使用Promise.reject

return asyncIsPermitted()
    .then(function(result) {
        if (result === true) {
            return true;
        }
        else {
            return Promise.reject(new PermissionDenied());
        }
    });
Run Code Online (Sandbox Code Playgroud)

用投掷

return asyncIsPermitted()
    .then(function(result) {
        if (result === true) {
            return true;
        }
        else {
            throw new PermissionDenied();
        }
    });
Run Code Online (Sandbox Code Playgroud)

我的偏好是throw仅仅因为它更短而使用,但是想知道一个是否有任何优势.

use*_*654 284

使用一个与另一个没有任何优势,但是,有一个特定的情况throw将无法工作.但是,这些案件可以修复.

每当你进入一个承诺回调,你就可以使用throw.但是,如果您在任何其他异步回调中,则必须使用reject.

例如,

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
});
Run Code Online (Sandbox Code Playgroud)

不会触发捕获,而是留下未解决的承诺和未捕获的异常.这是您想要改为使用的情况reject.但是,你可以通过宣告超时来解决这个问题:

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!'
});
Run Code Online (Sandbox Code Playgroud)

  • 值得一提的是,你不能使用`throw error`的非promisified异步回调中的地方,你也不能使用`返回Promise.reject(错误)`这是OP要求我们比较的东西.这基本上就是为什么你不应该在promises中放入异步回调.宣传所有异步的东西,然后你没有这些限制. (46认同)
  • "但是,如果你在任何其他类型的回调中"真的应该"但是,如果你在任何其他类型的_asynchronous_回调".回调可以是同步的(例如使用`Array#forEach`),并且那些回调可以起作用. (8认同)
  • @KevinB阅读这些行“在特定情况下,投掷将不起作用。” 和“任何时候在promise回调中,都可以使用throw。但是,如果您在任何其他异步回调中,则必须使用reject。” 我觉得示例片段将显示“ throw”不起作用的情况,而“ Promise.reject”是一个更好的选择。但是,这些片段不受这两种选择的影响,并且无论您选择什么,其结果都相同。我想念什么吗? (2认同)
  • @KevinB 我的意思是,对于任何片段,无论您是使用 `throw` 还是 `Promise.reject`,都会得到完全相同的行为。例如,没有捕获错误的代码段 1 不会捕获它,无论您是使用了 `throw 'or nah'` 还是使用了 `return Promise.reject('or nah')`。 (2认同)
  • 是。如果在setTimeout中使用throw,则不会调用catch。您必须使用传递给`new Promise(fn)`回调的`reject`。 (2认同)
  • @KevinB 哦。现在我看到了混乱的根源。我一直在阅读 OP 询问的所有带有 `Promise.reject()` 的示例,但是你的答案中的重点是使用传递给 `Promise` 构造函数的 `reject`。 (2认同)
  • @KevinB感谢您的光临。OP给出的示例提到他特别想比较return return Promise.reject()和throw。他没有提到new Promise(function(resolve,reject))结构中给出的`reject`回调。因此,虽然您的两个代码片段正确地说明了何时应该使用resolve回调,但是OP的问题不是那样。 (2认同)

luk*_*yer 180

另一个重要的事实是,reject() DOES NOT像终止控制流return语句一样.相反throw,终止控制流程.

例:

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

VS

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"));
Run Code Online (Sandbox Code Playgroud)

  • 那么这一点是正确的,但比较是棘手的.因为通常你应该通过写`return reject()`来返回被拒绝的承诺,所以下一行不会运行. (42认同)
  • 在这种情况下,`return reject()`只是`reject()的简写; 返回`即你想要的是终止流程.没有使用_executor_(传递给`new Promise`的函数)的返回值,所以这是安全的. (28认同)
  • 你为什么要退货呢? (5认同)
  • @223seneca拒绝只是一个普通的javascript函数,就像任何其他函数一样,因此它不能终止流程,因为函数通常不应该能够终止其调用者。 (3认同)
  • 这件事让我绊倒了一段时间。`reject()` 是否有任何充分的理由不终止流程?似乎应该如此。 (2认同)

小智 46

是的,最大的区别是reject是一个回调函数,它在promise被拒绝后执行,而throw不能异步使用.如果您选择使用reject,您的代码将继续以异步方式正常运行,而throw将优先完成解析器功能(此功能将立即运行).

我见过的一个例子帮助我澄清了问题,你可以设置一个带有拒绝的超时功能,例如:

new Promise(_, reject) {
 setTimeout(reject, 3000);
});
Run Code Online (Sandbox Code Playgroud)

以上可能无法用throw写.

在你的小例子中,差异无法区分,但在处理更复杂的异步概念时,两者之间的差异可能是极大的.

  • 这听起来像一个关键概念,但我不理解它的书面意思。我猜对 Promise 来说还是太新了。 (4认同)
  • @DavidSpector - 不,我真的非常熟悉承诺,而且我也很难理解上面解释的内容。:-) 除非它谈论的是同一件事 [Kevin B](/sf/answers/2341220381/) 在上述内容之后发布了一点。当然,关于“优先考虑”某些事情的内容尚不清楚。金发女郎,你想澄清一下吗? (4认同)
  • OP **不是**询问 Promise 构造函数。他询问如何在 .then()** 内抛出错误。有两种方法可以在 .then() 中抛出错误 - 使用 `throw` 或 `return Promise.reject()`。**两者同步工作** (2认同)

max*_*ell 38

TLDR: 当函数有时返回一个promise并且有时会引发异常时,它很难使用.在编写异步函数时,更喜欢通过返回被拒绝的promise来表示失败

您的特定示例模糊了它们之间的一些重要区别:

因为你的错误处理一个承诺链,抛出的异常都会自动转换到拒绝承诺.这可以解释为什么它们似乎可以互换 - 它们不是.

考虑以下情况:

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')
    }
  }
Run Code Online (Sandbox Code Playgroud)

这将是一种反模式,因为您需要同时支持异步和同步错误情况.它可能看起来像:

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
} 
Run Code Online (Sandbox Code Playgroud)

不好,这里正是在Promise.reject全球范围内可用的地方(并在全球范围内提供)并有效区分自己throw.重构现在变成:

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}`
    }
  })
}
Run Code Online (Sandbox Code Playgroud)

现在,您可以只使用一个catch()用于网络故障,使用缺少令牌的同步错误检查:

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

  • 然而,Op 的例子总是返回一个承诺。问题是当你想返回一个被拒绝的 promise(一个将跳转到下一个 `.catch()` 的 promise)时,你应该使用 `Promise.reject` 还是 `throw`。 (5认同)
  • 很好的答案,但我在这里发现了一个缺陷 - 这种模式假设所有错误都是通过返回 Promise.reject 来处理的 - 所有可能从 checkCredentials() 抛出的意外错误会发生什么? (3认同)
  • 我不明白@maxwell 的情况。难道你不能直接构造它,这样你就可以执行“checkCredentials(x).then(onFulfilled).catch(e) {}”,并让“catch”同时处理拒绝情况和抛出的错误情况吗? (2认同)

T.J*_*der 11

有一个区别 - 这应该无关紧要 - 其他答案没有涉及,所以:

如果执行处理程序传递给thenthrows,则该调用返回的承诺将then被抛出的内容拒绝。

如果它返回一个被拒绝的承诺,调用返回的承诺将then解析为该承诺(并且最终将被拒绝,因为它被解析为的承诺被拒绝),这可能会引入一个额外的异步“滴答”(在微任务队列,用浏览器术语来说)。

但是,任何依赖于这种差异的代码都从根本上被破坏了。:-) 它不应该对承诺结算的时间那么敏感。

下面是一个例子:

function usingThrow(val) {
    return Promise.resolve(val)
        .then(v => {
            if (v !== 42) {
                throw new Error(`${v} is not 42!`);
            }
            return v;
        });
}
function usingReject(val) {
    return Promise.resolve(val)
        .then(v => {
            if (v !== 42) {
                return Promise.reject(new Error(`${v} is not 42!`));
            }
            return v;
        });
}

// The rejection handler on this chain may be called **after** the
// rejection handler on the following chain
usingReject(1)
.then(v => console.log(v))
.catch(e => console.error("Error from usingReject:", e.message));

// The rejection handler on this chain may be called **before** the
// rejection handler on the preceding chain
usingThrow(2)
.then(v => console.log(v))
.catch(e => console.error("Error from usingThrow:", e.message));
Run Code Online (Sandbox Code Playgroud)

如果你运行它,在撰写本文时,你会得到:

使用抛出错误:2 不是 42!
usingReject 错误:1 不是 42!

注意顺序。

将其与相同的链进行比较,但都使用usingThrow

Error from usingThrow: 2 is not 42!
Error from usingReject: 1 is not 42!

这表明拒绝处理程序以其他顺序运行:

使用抛出错误:1 不是 42!
使用抛出错误:2 不是 42!

我在上面说“可能”是因为在其他领域有一些工作在其他类似情况下删除了这种不必要的额外勾选,如果所有涉及的承诺都是本机承诺(不仅仅是 thenables)。(具体来说:在一个async函数中,return await x最初引入了额外的异步滴答,return x而其他方面相同;ES2020 对其进行了更改,以便如果x是本机承诺,则在没有其他区别的情况下删除额外的滴答。)

同样,任何对承诺的结算时间如此敏感的代码都已经被破坏了。所以真的不重要/不应该重要。

实际上,正如其他答案所提到的:

除此之外,这主要是风格/偏好的问题,因此与大多数风格/偏好一样,与您的团队达成一致(或者您不关心任何一种方式),并保持一致。


Chr*_*ahl 5

一个例子来尝试。只需将isVersionThrow更改为false即可使用拒绝而不是引发。

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)
})
Run Code Online (Sandbox Code Playgroud)


Ven*_*tor 5

区别在于三元运算符

  • 您可以使用
return condition ? someData : Promise.reject(new Error('not OK'))
Run Code Online (Sandbox Code Playgroud)
  • 你不能使用
return condition ? someData  : throw new Error('not OK')
Run Code Online (Sandbox Code Playgroud)

  • [他们正在努力...](https://github.com/tc39/proposal-throw-expressions) :-) (5认同)