JavaScript承诺-拒绝和抛出

我已经阅读了几篇关于这个主题的文章,但我仍然不清楚Promise.reject和抛出错误之间是否有区别。例如,

使用Promise.reject

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

使用扔

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

我更倾向于使用throw,因为它更短,但我想知道其中一个是否比另一个有任何优势。

324796 次浏览

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

我看到的一个例子帮助我澄清了这个问题,你可以用reject设置Timeout函数,例如:

new Promise((resolve, reject) => {
setTimeout(()=>{reject('err msg');console.log('finished')}, 1000);
return resolve('ret val')
})
.then((o) => console.log("RESOLVED", o))
.catch((o) => console.log("REJECTED", o));

上面的代码不能用throw来写。

try{
new Promise((resolve, reject) => {
setTimeout(()=>{throw new Error('err msg')}, 1000);
return resolve('ret val')
})
.then((o) => console.log("RESOLVED", o))
.catch((o) => console.log("REJECTED", o));
}catch(o){
console.log("IGNORED", o)
}

在OP的小例子中,差异是难以区分的,但当处理更复杂的异步概念时,两者之间的差异可能是巨大的。

使用其中一个与另一个相比没有任何优势,但是,在throw将不起作用的特定情况下。然而,这些情况是可以解决的。

在promise回调中,可以使用throw。然而,如果你在任何其他异步回调中,你必须使用reject

例如,这不会触发catch:

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
});

相反,留给您的是一个未解决的承诺和一个未捕获的异常。在这种情况下,您可能希望使用reject。但是,您可以通过两种方式解决这个问题。

  1. 通过在超时内使用原来的Promise的拒绝函数:

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

  1. 通过承诺超时:

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!'
});

另一个重要的事实是,reject() return语句一样终止控制流。相反,throw终止了控制流。

例子:

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

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"));

# EYZ0 # EYZ1

你的例子混淆了它们之间的一些重要区别:

因为你在错误处理内部承诺链,抛出的异常会自动得到转换被拒绝的承诺。这也许可以解释为什么它们看起来是可以互换的——实际上并非如此。

考虑以下情况:

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')
}
}

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

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
}

不好,这里正是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}`
}
})
}

这现在让你只使用一个catch()的网络故障< em >和< / em >的同步错误检查缺乏令牌:

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

一个要尝试的例子。只需将isVersionThrow更改为false以使用reject而不是throw。

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)
})

区别在于三元运算符

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

有一个区别——这应该不重要——其他答案都没有涉及到,所以:

如果传递给then的实现处理程序抛出,则调用then返回的承诺连同抛出的内容一起被拒绝。

如果它返回一个被拒绝的承诺,调用then返回的承诺就是该承诺的决心(并且最终将被拒绝,因为它解决的承诺已被拒绝),这可能会引入一个额外的async "(用浏览器术语来说,这是微任务队列中的又一个循环)。

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

这里有一个例子:

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));

如果你运行它,在写这篇文章时,你会得到:

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

注意顺序。

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

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


usingThrow(1)
.then(v => console.log(v))
.catch(e => console.error("Error from usingThrow:", e.message));


usingThrow(2)
.then(v => console.log(v))
.catch(e => console.error("Error from usingThrow:", e.message));

这表明拒绝处理程序以另一种顺序运行:

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

我说“可能”;因为在其他领域已经有一些工作,在其他类似的情况下删除了这个不必要的额外标记如果,所有涉及的承诺都是本机承诺(不仅仅是thenable)。(具体来说:在async函数中,return await x最初引入了一个额外的异步标记,而return x在其他方面是相同的;ES2020改变了它,如果x是原生承诺,那么在没有其他区别的地方,额外的勾将被删除。)

同样,任何对承诺的结算时间如此敏感的代码都是已经碎了。所以这真的不重要。

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

  • 作为Kevin B指出throw将不能工作,如果你在回调到其他函数,你已经在你的实现处理程序中使用了-这是最大的问题
  • 作为Lukyer指出throw突然终止函数,这可能很有用(但在示例中使用的是return,它做同样的事情)
  • 作为文特指出,你不能在条件表达式(? :)中使用throw,至少可以使用现在还没有

除此之外,这主要是风格/偏好的问题,所以对于大多数情况,你要与你的团队达成一致(或者你不在乎任何一种方式),并保持一致。