刚在知乎上看到一道面试题。
某个应用模块由一个显示区域,以及按钮 A,按钮 B 组成。点击按钮 A,会向地址 uriA 发出一个
ajax 请求,并将返回的字符串填充到显示区域中,点击按钮 B,会向地址 uriB 发出一个ajax 请
求,并将返回的字符串再次填充到显示区域中(覆盖原有的数据)。
当用户依次点击按钮 A、B 的时候,预期的效果是显示区域依次被 uriA、uriB 返回的数据填充,
但是由于到 uriA 的请求返回比较慢,导致 uriB 返回的数据被 uriA 返回的数据覆盖了,与用户预
期的顺序不一致。
请问如何设计代码,解决这个问题?至少说出两种方式
这个问题是我们在开发中经常会碰到的,以下提供三种思路,用Vue代码做演示。
- 方法1:给每个请求做标记。
- 方法2:将多个请求按顺序串联。
- 方法3:提供取消方法,发送下一个请求时取消上个请求的回调。
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<div class="space-x">
<button @click="clickA">A</button>
<button @click="clickB">B</button>
</div>
<div class="space-x">
<button @click="clickA1">A1</button>
<button @click="clickB1">B1</button>
</div>
<div class="space-x">
<button @click="clickA2">A2</button>
<button @click="clickB2">B2</button>
</div>
</div>
</template>
<script>
// 请求
const reqA = () => {
return new Promise((resolve) => {
setTimeout(() => resolve("A"), 2000);
});
};
const reqB = () => {
return new Promise((resolve) => {
setTimeout(() => resolve("B"), 1000);
});
};
// 方法3:取消封装
const cancelable = (req, callback) => {
let cb = callback;
req().then((value) => {
cb && cb(value);
});
const cancel = () => {
cb = undefined;
};
return cancel;
};
export default {
name: "HelloWorld",
data() {
return {
msg: "i",
// 方法1:最后一个请求的标记
current: 0,
// 方法2:串联后的Promise
currentPromise: Promise.resolve("i"),
// 方法3:上个请求的取消方法
lastCancel: undefined,
};
},
methods: {
// 方法1
clickA() {
const idx = ++this.current;
reqA().then((value) => {
if (idx === this.current) {
this.msg = value;
}
});
},
clickB() {
const idx = ++this.current;
reqB().then((value) => {
if (idx === this.current) {
this.msg = value;
}
});
},
// 方法2
clickA1() {
this.currentPromise = this.currentPromise
.then(() => reqA())
.then((value) => {
this.msg = value;
});
},
clickB1() {
this.currentPromise = this.currentPromise
.then(() => reqB())
.then((value) => {
this.msg = value;
});
},
// 方法3
clickA2() {
this.lastCancel && this.lastCancel();
this.lastCancel = cancelable(reqA, (v) => (this.msg = v));
},
clickB2() {
this.lastCancel && this.lastCancel();
this.lastCancel = cancelable(reqB, (v) => (this.msg = v));
},
},
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
.space-x > *:not(:first-child) {
margin-left: 10px;
}
.space-x + .space-x {
margin-top: 10px;
}
</style>