最近面试的时候遇到了一个关于ajax处理顺序的问题,当时没有想到自己感觉满意的思路,回家后想了一个自己还算满意的思路,记录一下。
题目
页面有两个按钮 A和B,以及一个输入框,A按钮点击后发送一个请求,返回一个字符串A,B也发请求,但返回字符串B,返回后会把字符串赋值给输入框,但是A,B发送的两个请求返回的时间不同,点击两个按钮的顺序也不一定,B要比A先返回,而最终效果要求是输入框字符的顺序是AB。 然后题目给了点样板代码,我记得不是很清楚了,大致是这些:
function ajaxA() // 返回promise
function ajaxB() // 返回promise
function setText(text)
btnA.onclick=function(){
}
btnB.onclick=function(){
}
复制代码
当时大致是这些吧,然后面试官说ajaxA,ajaxB,一个setText不需要我们实现也就是我们不用考虑他们,所以我们自己实现的重点是两个点击事件里面怎么写,可以加点代码。
当时的思路
我当时脑海中就想到了Promise.all因为我看到两个请求返回了promise,而且根据题目要求的话我得等两个请求都返回了再设置输入框,同时在all里面按顺序放入A promise和B promise,可当时就被面试官否定了,他说ajax返回的时间顺序不定...反正意思是你在all最后拿到的顺序是错的跟你传入的数组的顺序可能不一致,我当时脑子还真没印象这个顺序到底是不是跟我在all里面传入的数组循序一致,因为我还真没用过这个all,当时心一下虚了,毕竟是面试官嘛。 当时我还有另个思路就是同步请求,当然这是最简单了,按顺序写代码就可以了。
后来的思路
回家后我试了试Promise.all,发现最后返回的数组顺序是根据我传入的数组的顺序,nnd,面试官怎么说不一致呢?
var p3 = new Promise((resolve, reject) => {
setTimeout(resolve, 500, 'foo');
});
var p2 = new Promise((resolve, reject) => {
setTimeout(resolve, 1000, 'p2');
});
Promise.all([ p2, p3]).then(values => {
console.log(values); // [ 'p2', "foo"]
});
复制代码
测试看到虽然 p3比p2先resolve,但是由于我传入的顺序是p2,p3所以最后values依然是按照p2 ,p3 的顺序把结果放入了values。wtf,面试官坑我?
另一个思路:回家后我一直在想不用Promise.all怎么搞定。 顺着一个线索我要用promise, 我怎么在A 返回的时候等A先赋值,B再赋值,B怎么等待A呢?
然后给室友说了下,他立马说我等A请求成功后再发送B请求,恩,我觉得思路不好,回调地狱啊,另外按照题目要求貌似是点了按钮就立马发请求,两个按钮点击后发请求是没有影响的,不能让A阻塞B请求的发送,但是我们可以让A阻塞B设置输入框值的步骤。
当时脑子里一致在想利用promise 等A返回后我调用一下resolve,B就开始设置输入框的值,但是最终点击事件里面可能是这样子的
btnA.onclick = function () {
ajaxA().then(res=>{
setText(res)
})
}
btnB.onclick = function () {
ajaxB().then(res=>{
setText(res) //要等A设置完再设置B
})
}
复制代码
一般我们利用promise是这个姿势
let p=new Promise((resolve,reject)=>{
ajaxA().then(res=>{
resolve(res)// 触发p then里面的回调执行
})
})
p.then(()=>{
//等待调用resolve 后执行
})
复制代码
这里我不能在点击事件里声明promise p,因为 有可能先点击B,如果此时没点击A的话,p是undefiend,然后脑子开始短路了,思考一会,忽然想起了 vue 中的nextTick,想到了源码在处理nextTick的回调时的代码,我也不知道怎么会想到nextTick的,灵光一现吧,然后赶紧翻了下源码:
看到源码里 new 一个Promise并暂时保存了resolve,这样这个resolve就可以在外面任意调用了,我当时思路就卡在我在A的点击事件里面 怎么去调用一个resolve去控制B的then,万万没想到可以保存resolve啊,这样子的话实现思路就很简洁了。
function setText(text){
ipt.value = ipt.value+text
}
let _resolve
const promise=new Promise((resolve,reject)=>{
_resolve=resolve
})
btnA.onclick = function () {
ajaxA().then(res=>{
setText(res.data)
_resolve()
})
}
btnB.onclick = function () {
ajaxB().then(res=>{
promise.then(()=>{
setText(res.data)
})
})
}
复制代码
这就是我满意的思路啦,只增加了一个Promise 和一个_resolve变量。当然会有其他很多思路,我室友也写了下,但是他写的恰恰是我最不想写的思路,加了好几个变量,又是数组保存,又是条件判断的。
function setText(text){
ipt.value = text
}
let doSth = state =>{
if (!(state.a && state.b)) return;
setText(state.prop.join(' '))
}
let state = {prop:[],a:false,b:false}
btnA.onclick = function () {
ajaxA().then(res=>{
state.a = true
state.prop[0] = res.data
doSth(state)
})
}
btnB.onclick = function () {
ajaxB().then(res=>{
state.b = true
state.prop[1] = res.data
doSth(state)
})
}
复制代码
我室友的大致思路,还强行改了一波setText函数,他最后还手动规定了数据放的顺序,他说他的可扩展强...我没看出来,并且代码不简洁。
最后
不用Promise.all你们还有其他优雅思路吗?我第一次处理这类问题,想了解下有没有其他思路,请大佬们说下