从 JavaScript 角度学 Python(25) - 例外处理

前言

接下来我们要聊一个非常非常重要的东西,也就是关於错误的处理,而这个处理又称之为例外处理。

例外处理概念

首先先聊聊例外处理是什麽,通常我们在开发系统的时候不免会遇到一些错误状况,例如:使用者网路不稳定、使用者输入错误的资料、忘记宣告变数、输入到一半台风来吹断电线杆停电或者是上网到一半被隔壁屁孩射断网路线这种诸如此类的状况(之前真的有朋友跟我说他的网路线被屁孩射断),因此如果没有做好一些错误机制的防范的话,那麽系统在上线时是很容易降低客户对於该系统的信任度,所以错误的处理上是非常重要的。

那麽为了避免这种状况我们通常就会使用例外处理的方式处理,如果已经有开始撰写 ES7 语法的人,相信非常的熟悉 asyncawait 语法,想当然你也一定会使用 try...catch 语法,下面简单写一小段:


async function getData () {
  try{
    const res = await axios.get('xxxx');
  } catch (error) {
    console.error(error);
  }
}

上面的程序码简单来讲,就是当 AJAX 因意外失败或者网路中断时,就会自动改跑 catch 区块,但是这边要注意如果你是用於前端画面给使用者看的话,请不要使用 console.log 当作错误提示,因为普通的使用者根本不知道什麽是网页开发者工具(devToole),因此这边只是一个范例程序码而已。

https://ithelp.ithome.com.tw/upload/images/20210925/20119486tBgyp1rQwW.png

而上方是一个简单的例外错误处理,接下来我们将会进入 Python 环节了解 Python 的例外处理是如何撰写的唷!

例外处理

一开始先我们刻意制造一个错误状况:

def sayHello():
  print('Hello')

print(myName)

sayHello()

上面的程序码看起来很正常对吧?但实际上执行是会出现「NameError: name 'myName' is not defined」的错误唷~

https://ithelp.ithome.com.tw/upload/images/20210925/201194862Hip6kj9FW.png

oh!如果你问我为什麽会出现这个错的话,罚你回去「从 JavaScript 角度学 Python(4) - 型别与变数」章节重看,因为这章节我有解释唷~

那麽如果你这边有使用例外处理的话,就可以避免程序码因为错误而导致中断,而让後面其他的程序码可以正常运作:

def sayHello():
  print('Hello')

try:
  print(myName)
except:
  print('你忘记宣告变数了。')

sayHello()

https://ithelp.ithome.com.tw/upload/images/20210925/20119486zAJDUn7Tc0.png

针对错误类型的例外处理

除此之外你还可以针对错误类型来给予例外处理,什麽意思呢?举例来讲刚刚的范例程序码会出现「NameError: name 'myName' is not defined」的错误讯息,而这个错误类型是 NameError,所以你可以特别针对 NameError 客制化例外处理:

def sayHello():
  print('Hello')

try:
  print(myName)
except NameError:
  print('你忘记宣告变数了。')
except:
  print('单纯只是一个错误。')

sayHello()

但是错误类型的例外处理非常非常多种,所以我建议可以阅读这一篇了解还有哪些常见的错误类型。

那 JavaScript 有没有类似这种针对错误类型的例外处理呢?

基本上有,只是写法上比较不一样,JavaScript 是必须使用 instanceof 语法来判断错误类型,举例来讲,当你语法没有宣告就存取的话是会出现 ReferenceError 错误,因此写法如下:

try {
  console.log(Ray); // 没有宣告的变数,会出现 「Uncaught ReferenceError: Ray is not defined」
} catch(error) {
  if(error instanceof ReferenceError) {
    console.log('你存取一个不存在的变数唷');
  }
}

console.log('程序码依然正常运作');

那麽透过上述两种程序码来讲,你比较喜欢哪一种呢~

https://ithelp.ithome.com.tw/upload/images/20210925/20119486PlJ5WLEfxq.png

finally 处理

还有一种处理是 finally,通常这个最常见於跟远端服务器要资料之後要做特定的事情,举例来讲我们在撰写 Vue 的时候,通常会使用 Loading 套件来解决在跟远端服务器 AJAX 过程的等待时间,进而尽可能的提升使用者体验,这边我就举例一个很常见不好写法:

const app = {
  data() {
    return {
      isLoading: false,
    };
  },
  methods:{
    async getData() {
      try {
        this.isLoading = true;
        await axios.get('xxxxx');
        this.isLoading = false;
      } catch (error) {
        console.log(error)
      }
    },
  },
};

Vue.createApp(app).mount('#app');

上面写法看似不好的原因在於,当 AJAX 若失败的话,就会进入到例外处理 catch,但是关闭 isLoading 的方式却是在 try 中,因此这个 Loading 状态就永远不会关闭,有些人会乾脆 trycatch 都撰写 this.isLoading = false; 就像下方一样:

const app = {
  data() {
    return {
      isLoading: false,
    };
  },
  methods:{
    async getData() {
      try {
        this.isLoading = true;
        await axios.get('xxxxx');
        this.isLoading = false;
      } catch (error) {
        console.log(error)
        this.isLoading = false;
      }
    },
  },
};

Vue.createApp(app).mount('#app');

但是你有想过吗?今天只是一个 this.isLoading = false; 可能还感觉不出什麽,但若程序码逻辑较复杂、较多的话,难道你两处都要写吗?

所以这边就要来介绍 finally,只要你使用 finally 的话,那麽就只会需要写一次即可:

const app = {
  data() {
    return {
      isLoading: false,
    };
  },
  methods:{
    async getData() {
      try {
        this.isLoading = true;
        await axios.get('xxxxx');
      } catch (error) {
        console.log(error)
      } finally {
        this.isLoading = false;
      }
    },
  },
};

Vue.createApp(app).mount('#app');

因为不管成功与否 finally 必定就是会执行,这样子也可以确保如果请求失败时,也可以正常的关闭 Loading 状态。

当然 Python 也是一样的做法,不管怎麽样如果你有一个必定要执行的动作,例如:关闭档案或者是输出某些讯息,那就可以使用 finally 来解决:

def sayHello():
  print('Hello')

try:
  print(myName)
except NameError:
  print('你忘记宣告变数了。')
except:
  print('单纯只是一个错误。')
finally:
  print('这是一定会执行的行为。')

sayHello()

所以你也可以把 finally 理解成「必定会执行」的区块,这样你学会了吗?

https://ithelp.ithome.com.tw/upload/images/20210925/20119486qenOWw4z2d.png

抛出

聪明的你或许马上就会联想到 JavaScript 的 throw,有一种状况是比较特别,举例来讲我们在做 AJAX 新增、修改与删除等操作时,後端可能不会吐相对应的 HTTP Code,有可能是吐给你 status 来判断这一次新增资料的结果是 true or false,所以这时候我们就会使用 throw 将当前的状态踢出到例外处理。

那为什麽会有这种状况呢?假使後端不管成功与否都是吐给你 200 状态码,那 AJAX 判断上当然会认为你是请求成功的,并不属於失败,所以就不会走 catch 的部分,以 JavaScript 角度来讲,我们可能就会这样写:

const app = {
  data() {
    return {
      isLoading: false,
    };
  },
  methods:{
    async getData() {
      try {
        this.isLoading = true;
        const res = await axios.get('xxxxx');
        if(!res.data.status) {
          throw Error('错误');
        }
      } catch (error) {
        console.log(error)
      } finally {
        this.isLoading = false;
      }
    },
  },
};

Vue.createApp(app).mount('#app');

透过上面范例我们可以自己决定何时要抛出,但是在 Python 中就没有 throw 的语法,取而代之的是使用 raise 语法,raise 非常好玩,如果你不加上要喷什麽样的错误的话,预设会是抛出 RuntimeError,你可以试着单独执行 raise 看看:

raise # RuntimeError: No active exception to reraise

因此透过这个预设的特性就可以使用前面所学的技巧设置一个例外错误讯息:

status = True

try:
  if(status):
    raise
except RuntimeError:
  print('Error', RuntimeError)
except:
  print('单纯只是一个错误。')
finally:
  print('这是一定会执行的行为。')

如果以上错误的处理你还不满意的话,你也可以自己建立一个 class 来自定属於自己的例外,而这一点建议可以直接参考官方文件。

那麽今天关於例外处理的介绍也就到这边告一个段落哩。

作者的话

完蛋了,今天的「作者的话」我完全不知道该打什麽,想分享一下醉鸡的制作过程,但是我发现前面我已经分享过了,後来思考後想再分享小瓦机器人被我捏爆的事情,结果我昨天也讲过了,这一篇只好空下「作者的话」了。

参考文献

关於兔兔们

兔法无边


<<:  观光产业的多元启发

>>:  Day12 [实作] 使用浏览器来拍照并加上滤镜

【Day 04】- 今天来把 Module 藏起来(基於 PEB 断链,隐藏 DLL 的方法)

Agenda 资安宣言 测试环境与工具 前情提要 学习目标 技术原理与程序码 References ...

Day13语法样式(CSS)

CSS语法样式 CSS的全名是Cascading Style Sheets,阶层样式表指的是可以在同...

K8s - Kubernetes 指令参考笔记

K8s - Kubernetes 指令参考笔记 参考资料 参考资料1:Day 11 Kubernet...

[自学笔记] URL Encoding

URL Encoding(URL编码) URL 编码将字符转换为可以通过 Internet 传输的格...

[day29] - Angular Component to Web Component

後来发现 , 之前说明了 Vue . React Component 如何变成 Web Compon...