使用await-to-js优雅地解决使用await等待的promise的异常处理

1.学习内容和准备工作

学习内容:

github: github.com/scopsy/awai…

官方博客:blog.grossman.io/how-to-writ…

准备工作:

1.clone代码  git clone  https://github.com/scopsy/await-to-js
2.安装依赖 npm  i
3.打包 npm run build (源码是用ts写的,打包后有js版本)
复制代码

2.学习过程

先看一下是什么,怎么用:

import to from 'await-to-js';

async function asyncFunctionWithThrow() {
  const [err, user] = await to(UserModel.findById(1));
  if (!user) throw new Error('User not found');
}
复制代码

可以看到使用to装饰之后的调用,还是用await发请求,但是直接就可以解构错误信息,而不用通过.catch的方式,是不是很方便呢?看一下源码的实现吧~

2.1 源码解读

ts代码的位置: src\await-to-js.ts

/**
 * @param { Promise } promise
 * @param { Object= } errorExt - Additional Information you can pass to the err object
 * @return { Promise }
 */
export function to<T, U = Error> (
  promise: Promise<T>,
  errorExt?: object
): Promise<[U, undefined] | [null, T]> {
  return promise
    .then<[null, T]>((data: T) => [null, data])
    .catch<[U, undefined]>((err: U) => {
      if (errorExt) {
        const parsedError = Object.assign({}, err, errorExt);
        return [parsedError, undefined];
      }

      return [err, undefined];
    });
}

export default to;
复制代码

打包后js代码位置:dist\await-to-js.es5.js

/**
 * @param { Promise } promise
 * @param { Object= } errorExt - Additional Information you can pass to the err object
 * @return { Promise }
 */
function to(promise, errorExt) {
    return promise
        .then(function (data) { return [null, data]; })
        .catch(function (err) {
        if (errorExt) {
            var parsedError = Object.assign({}, err, errorExt);
            return [parsedError, undefined];
        }
        return [err, undefined];
    });
}

export { to };
export default to;
//# sourceMappingURL=await-to-js.es5.js.map
复制代码

参数为promise和可选错误信息对象;then中返回[null,data] ,null表示没有发错错误(异常),data为实际的数据;catch中判断是否提供了额外的错误对象,如果有则合并promise返回的异常和错误对象,然后返回[parsedError, undefined]表示发生了错误没有数据。如果没有提供额外的错误对象,则返回promise返回的异常和undefined,即[err, undefined]。最后导出一个对象以及默认导出,为了支持不同的导入方式。

2.2 测试

function to(promise, errorExt) {
    return promise
        .then(function (data) { return [null, data]; })
        .catch(function (err) {
        if (errorExt) {
            var parsedError = Object.assign({}, err, errorExt);
            return [parsedError, undefined];
        }
        return [err, undefined];
    });
}
async function func(){
	const testInput = 41;
	const promise = Promise.resolve(testInput);
	const [err, data] = await to(promise);
	console.log(err, data)
	// null 41
}
func()

async function func2(){
	const promise = Promise.reject('Error');
	const [err, data] = await to(promise);
	console.log(err, data)
	// Error undefined
}
func2()

async function func3(){
	const promise = Promise.reject({ error: 'Error message' });
	const [err, data] = await to(promise, {extraKey: 1});
	console.log(err, data)
	// {error: 'Error message', extraKey: 1}  undefined
}
func3()


async function func4(){
	const promise = Promise.resolve({ name: '123' });
	const [err, data] = await to(promise);
	console.log(err, data)
	// null {name: '123'}
}
func4()
复制代码

2.3 一个简版的实现

此简版的实现摘自官方的介绍文档:

export default function to(promise) {
   return promise.then(data => {
      return [null, data];
   })
   .catch(err => [err]);
}
复制代码

2.4 学以致用

项目中的应用我需要把根据图片或者视频的url获取其blob数据,写了这样的一段代码:

//获取图片或者視頻的Blob值
export function getMediaBlob(url) {
  return new Promise((resolve, reject) => {
    var xhr = new XMLHttpRequest()
    xhr.open('get', url, true)
    xhr.responseType = 'blob'
    xhr.onload = function () {
      if (this.status == 200) {
        console.log('response', this.response)
        resolve(this.response)
      } else {
        reject('error')
      }
    }
    xhr.send()
  })
}
复制代码

实际运行时如果图片或者视频url所在服务端不允许跨域,就会发生了错误,导致下载不成功。虽然浏览器控制台提示发生了跨域,但是程序目前还是捕获不到无误的:

需要修改getMediaBlob增加onerror事件:

//获取图片或者視頻的Blob值
export function getMediaBlob(url) {
  return new Promise((resolve, reject) => {
    var xhr = new XMLHttpRequest()
    xhr.open('get', url, true)
    xhr.responseType = 'blob'
    xhr.onload = function () {
      if (this.status == 200) {
        console.log('response', this.response)
        resolve(this.response)
      } else {
        reject('error')
      }
    }
    xhr.onerror = function(e) {
      reject(e)
    }
    xhr.send()
  })
}
复制代码

修改后的代码可以检测到发生了错误,并且reject一下。那么如何使用发生的错误信息呢:

async downloadMedia() {
  const arr = this.getSelectedUrlAndType()
  if (!arr.length) {
    this.$message.warning('请先勾选要下载的文件')
    return
  }
  // 文件名/压缩包文件名:多媒体+下载时间(年月日时分秒)
  const time = this.moment().format('YYYYMMDDHHmmss')
  // 判断是下载一个文件还是下载多个文件
  if (arr.length === 1) {
    // 下载单个文件的情况
    const data = await getMediaBlob(arr[0].url)
    if (err) {
      this.$message.error('下载失败')
      return
    }
    const suffix = arr[0].mediaType === 0 ? 'png' : 'flv'
    saveBlobAsFile(data, `多媒体${time}.${suffix}`)
  } else {
    // 下载多个文件的情况
    this.handleBatchDownload(arr, `多媒体${time}.zip`)
  }
},
复制代码

getMediaBlob得到的data接下来要用,我又不想.then .catch 。这个时候 await-to-js就可以粉墨登场了!

import to from 'await-to-js';
async downloadMedia() {
  const arr = this.getSelectedUrlAndType()
  if (!arr.length) {
    this.$message.warning('请先勾选要下载的文件')
    return
  }
  // 文件名/压缩包文件名:多媒体+下载时间(年月日时分秒)
  const time = this.moment().format('YYYYMMDDHHmmss')
  // 判断是下载一个文件还是下载多个文件
  if (arr.length === 1) {
    // 下载单个文件的情况
    const [err, data] = await to(getMediaBlob(arr[0].url)) 
    if (err) {
      this.$message.error('下载失败')
      return
    }
    const suffix = arr[0].mediaType === 0 ? 'png' : 'flv'
    saveBlobAsFile(data, `多媒体${time}.${suffix}`)
  } else {
    // 下载多个文件的情况
    this.handleBatchDownload(arr, `多媒体${time}.zip`)
  }
},
复制代码

最终实现的效果:

学习源码的快乐莫过于学以致用,解决实际开发中的问题啦~

3.思考总结

总结: 这个库确实好用啊,虽然我开发的时候也不咋写try-catch,但是如果要写对promise异常处理的逻辑以后也不写try catch了, 也不用.then() .catch()了,直接用这个await-to-js了。

思考: 看到这个库的用法我第一反应是想到了react钩子函数:

const [count, setCount] = useState(0);
复制代码

钩子函数返回的值也是用数组接收。

此外我还想到了node.js中的回调函数的传参形式:

var fs = require("fs");

fs.readFile('input.txt', function (err, data) {
    if (err) return console.error(err);
    console.log(data.toString());
});

console.log("程序执行结束!");
复制代码

这不也是 err,data吗~

Guess you like

Origin juejin.im/post/7076243357256646663