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吗~