在JS中,总是“最后一个” return 语句获胜

1,893 阅读2分钟

优秀博文翻译系列

原文翻译自: Jake Archibald Blog

下面的js函数,哪个return 会执行?

function test() {
  return 'one';
  return 'two';
  return 'three';
}

你可能会说:“会执行第一个。”

但是我将尝试去说服你,将是“最后一个”return得到执行。

无需担心,上面的函数一定是返回one,但是在这个例子中,第一个return声明阻止了其他Return语句的执行。也就是说,此处实际上最后一个被执行的Return语句是return 'one',并且它是胜出者。当然,它确实是第一个声明的return,但我还是对的^_^。

我知道你在想:“闭嘴吧。”,但是请容我慢慢道来。

finally语句

finally 语句是这样一种东西:

function finallyTest() {
  try {
    console.log('one');
    return 'three';
  } catch (err) {
    console.log('error');
  } finally {
    console.log('two');
  }
}

console.log(finallyTest());
console.log('four');

上面的函数依次打印 'one''two''three''four'finally代码块中的内容总是会在try/catch执行结束后被执行,即使try/catchreturn返回。

我之前不经常在JavaScript中用finally,直到最近,我发现我需要在一个async函数中像这样使用它:

async function someAsyncThing() {
  startSpinner();

  try {
    await asyncWork();
  } catch (err) {
    if (err.name === 'AbortError') return;
    showErrorUI();
  } finally {
    stopSpinner();
  }
}

无论如何,激动人心的是,finally给了我们在一次函数调用中执行多次return的机会:

function manyHappyReturns() {
  try {
    return 'one';
  } finally {
    try {
      return 'two';
    } finally {
      return 'three';
    }
  }
}

并且,执行 manyHappyReturns() 的结果是 'three'

最后一个Return总是获胜者。

并不是在函数中最后一个出现的return会获胜,那将令人发疯。而是最后一个被执行的return语句。

最后一个Return会胜出,和最后一个变量赋值会胜出的原理相同。事实上,这一现象很像变量赋值的过程:return赋值给函数的结果,所以后面的return覆盖了前面的return。在Java和Python中也是这样。感谢Daniel Ehrenberg 让我理解了这个小技巧。

副作用是,从finally中执行return,会清除抛出的Error:

function catchThis() {
  try {
    throw Error('boom');
  } finally {
    return 'phew';
  }
}

执行catchThis() 的结果是: 'phew'.

额外福利: Promises

async函数与上面的普通函数在这一特性上表现相同(除了返回值被包装成promise)。然而,promise.finally()表现与之不同。

const promise = Promise.resolve('one').finally(() => 'two');

这里,promise被实现为'one'。这可能是因为promise的响应是回调函数,并且回调函数的调用者(在这里是promise本身),根本无法分辨一个函数到底是执行了return undefined还是完全没有执行return语句。因此,它不能模拟上面普通的finally特性,而仅仅是选择忽略了它。

虽然, promise.finally确实影响了promise被实现的时机

const wait = (ms) => new Promise((r) => setTimeout(() => r(), ms));

const promise = Promise.resolve('one').finally(async () => {
  await wait(2000);
  return 'two';
});

在这里,promise仍然被实现为'one',但是却是在2s之后。