问题背景

最近在用 uni-app 开发前端收银台页面,遇到一个问题:用户输入支付金额时,需要通过后台请求进行实时计价。如果用户快速连续输入金额,由于请求接口响应速度不同,可能先请求的接口后响应,导致计价显示错误。如用户快速输入 300 元时,可能最终看到的是 30 元的计价结果,这种情况是要不得的。那么我们应该如何解决?

解决方法

要解决上面的问题,就需要在每次发起新的计价请求时,取消或终止上一次未响应的请求。

uni.request

如果使用的是 uni.request 发起网络请求的。通过传入 success / fail / complete 中的一个或多个参数,即可返回一个 requestTask 对象。通过 requestTask.abort() 即可中断请求任务。这个比较简单,这边不做展开。

Promise

虽然说上述的 uni.request 方法,就可以实现网络请求和中断任务。但是实际使用中,为了实现请求的同步化,我们用的比较多的可能是对 uni.request 进行封装返回 Promise (该部分内容请参考 之前的文章)。

之前在学习 Promise 的时候,看到的 Promise.race (参见:JS 中 Promise 三剑客 Promise.all、Promise.race 和 Promise.allSettled)刚好可以派上用场。

顾名思义,Promise.race 是赛跑的意思,Promise.race 中的 Promise 数组中谁先返回成功或失败的结果,整体就返回那个结果。我们可以在下一次请求之前,通过 reject 模拟请求失败,结合 Promise.race 的“赛跑”功能,实现上次请求如未成功则直接失败处理,如果成功,则正常返回。具体代码如下:

实现方式:
以下代码根据实际情况,可放在页面组件或自己项目的公共函数库中。

createPromiseWithAbort(fetchPromise) { // 创建一个可终止的 Promise 对象
    let abort = null
    const abortPromise = new Promise((resolve, reject) => {
        abort = () => reject('abort')
    })
    let promiseWithAbort = Promise.race([fetchPromise, abortPromise])
    promiseWithAbort.abort = abort
    return promiseWithAbort
},

abortPromise(promiseWithAbort) { // 终止未完成的 Promise 对象 promiseWithAbort
    if (promiseWithAbort != null && promiseWithAbort.abort) {
        promiseWithAbort.abort()
    }
}

使用方式:

import http from '@/commons/http.js'

export default {
    data() {
        return {
            payDiscountPromise: null
        }
    },
    methods: {
        calculate() {
            // 终止未完成的 payDiscountPromise 请求
            this.$util.abortPromise(this.payDiscountPromise)

            // 计价 Promise 请求
            let doPricePromise = http('data/get1', {
                param: {
                }
            })

            // 返回可终止的 Promise 对象
            this.payDiscountPromise = this.$util.createPromiseWithAbort(doPricePromise)

            // 对计价结果进行处理,如果下次请求之前调用了 promiseAbort 且请求未完成将被终止
            this.payDiscountPromise.then(res => {
            }).catch(err => {
                console.error(err)
            })
        }
    }
}