关于主题
至于标题中throwing-function:
“在js中,可能抛出错误的函数,有没有对应的专业术语来形容这样的函数?”
在JavaScript中,可能抛出错误的函数通常被称为“throwing functions”(抛出函数)。——ChatGPT
如标题,这样文章主题应该就比较明确了
背景
概述
写这篇文章的背景是,作者在个人项目中对一些原生api进行二次封装,然后在业务代码去调用封装好的方法,业务代码中妥善的写了错误处理的逻辑,可是当原生api抛出错误时,我们得到了控制台报错而非进行错误处理的逻辑,如Message提示等。换句话说业务代码中没有感知到二次封装方法的抛出错误。
错误代码示意
如下getMediaStream函数本质就是对navigator.mediaDevices.getUserMedia这个原生api进行二次封装,增加一些额外的逻辑
import to from 'await-to-js';
// 获取媒体流
export async function getMediaStream(constraints: MediaStreamConstraints = { video: true, audio: true }) {
if (globalMediaStream) {
return globalMediaStream;
}
const [error, stream] = await to(navigator.mediaDevices.getUserMedia(constraints));
if (error) {
return new Error('获取本地媒体失败, 请检查是否开启了摄像头与麦克风');
}
return (globalMediaStream = stream);
}
Plus:其中to函数是一个很简单的小工具,可以让异步逻辑嵌套更浅,简易源码如下:
function to(promise) {
return promise
.then(function (data) {
return [null, data];
})
.catch(function (err) {
return [err, undefined];
});
}
我们在业务逻辑中使用getMediaStream如下:
const getMediaStreamConsumer = async () => {
// ...
const [err, stream] = await to(getMediaStream());
if (err) {
return ElMessage.error(err.message);
}
// ...other logic to use stream
}
分析
问题一、类型错误
首先经过如上封装,getMediaStream函数的返回值变成了MediaStream | Error,也就是原本navigator.mediaDevices.getUserMedia方法的返回值和Error的联合类型,这样我们在getMediaStreamConsumer函数中拿到stream去使用的时候还需要使用类型断言来排除Error干扰,如(stream as MediaStream).xxxFun()
问题二、to方法中失败的错误捕获
如果原生apinavigator.mediaDevices.getUserMedia执行报错,那么我们希望getMediaStreamConsumer中可以从to方法的返回值中解构得到err,进而触发ElMessage.error的错误提示,但是事实是err的值永远为undefined。
原因就在于我们二次封装throwing functions时没有保持它原来的返回值类型,无论是Error类型还是什么类型,getMediaStream函数永远拥有返回值,自然消费它的函数永远拿不到err,换句话说getMediaStreamConsumer里的to方法的执行过程中,没有错误被抛出。
解决方法
二次封装throwing functions时使用throw抛出错误来保持原api的特点,而不是return。
如下
import to from 'await-to-js';
export async function getMediaStream(constraints: MediaStreamConstraints = { video: true, audio: true }) {
if (globalMediaStream) {
return globalMediaStream;
}
const [error, stream] = await to(navigator.mediaDevices.getUserMedia(constraints));
if (error) {
// 抛出错误
throw new Error('获取本地媒体失败, 请检查是否开启了摄像头与麦克风');
}
return (globalMediaStream = stream);
}