背景:最近公司使用的图形验证码开始打起了广告,于是组内想着找个开源项目去加工一下,改造过程中遇到一个问题,开源项目的坐标是由前端随机生成的,不是很安全,改造成服务端生成的话,图形验证码的生成就有两个异步请求,一个是图片的加载,一个是服务端请求坐标,那么最好的就是两个异步请求同时发起,等待两个异步请求任务都完成之后再进行之后的业务逻辑。
一、简单梳理一下
其实问题用一句话描述就是“同时开始两个异步任务,两个异步任务完成之后执行后面的逻辑”,那么很自然的就想到了Promise.all()这个方法,但这里有一个问题就是图片的加载并不是一个Promise,我们有的只有onload和onerror这些回调接口,那么问题就变成了怎么让onload的时候resolve Promise。
二、在Promise构造函数外resolve Promise
经过上面的分析问题就变成了在Promise构造函数之外resolve Promise,大致思路有两种:
1. 将resolve和reject存起来,在需要的时候调用;
2. 在构造函数中设定onload和onerror回调事件;
第一种思路比较简单,但将resolve和reject暴露出去之后难以维护;第二种就要好一点,下面看看怎么实现吧。
// 这里用一个函数来封装一下onload和onerror的回调函数,在Promise里把resolve作为钩子函数传进去,这样可以把业务逻辑写在一起,比如可以在error中实现尝试重新获取或者读取本地图片
const loadHandle = function (hook) {
return () => {
if (hook && typeof hook === 'function') {
hook();
}
// 业务逻辑...
}
};
const errorHandle = function (hook) {
return () => {
if (hook && typeof hook === 'function') {
hook();
}
// 业务逻辑...
}
};
const src = '';
const img = new Image();
// 加载图片的Promise
const imgLoadPromise = new Promise((resolve, reject) => {
img.src = src;
img.onload = loadHandle(resolve);
img.onerror = errorHandle(reject);
});
// 获取坐标
const coordinatesPromise = new Promise((resovle, reject) => {
$.ajax({
type: "get",
async: true,
url: 'url',
data: "",
success: res => {
resovle(res);
},
error: err => {
reject(err);
}
})
});
Promise.all([imgLoadPromise, coordinatesPromise]).then((res) => { // res是Promise数组的resolve的结果
// 处理逻辑...
}).catch((err) => { // 一旦Promise数组中有一个reject了就会将这条错误结果给到失败状态的回调函数
// 错误处理...
})