使用Promise.race实现超时机制取消XHR请求

Posted star

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用Promise.race实现超时机制取消XHR请求相关的知识,希望对你有一定的参考价值。

我们来看一下如何使用Promise.race来实现超时机制。
当然XHR有一个 timeout 属性,使用该属性也可以简单实现超时功能,但是为了能支持多个XHR同时超时或者其他功能,我们采用了容易理解的异步方式在XHR中通过超时来实现取消正在进行中的操作。

让Promise等待指定时间

首先我们来看一下如何在Promise中实现超时。
所谓超时就是要在经过一定时间后进行某些操作,使用 setTimeout 的话很好理解。
首先我们来串讲一个单纯的在Promise中调用 setTimeout 的函数。

//delayPromise.js
function delayPromise(ms) {
    return new Promise(function (resolve) {
        setTimeout(resolve, ms);
    });
}

Promise.race中的超时

Promise.race就是一组promise中只要有一个返回,其它的promise就不会执行后续回调(无论错误还是成功)

var winnerPromise = new Promise(function (resolve) {
        setTimeout(function () {
            console.log(\'this is winner\');
            resolve(\'this is winner\');
        }, 4);
    });
var loserPromise = new Promise(function (resolve) {
        setTimeout(function () {
            console.log(\'this is loser\');
            resolve(\'this is loser\');
        }, 1000);
    });
// 第一个promise变为resolve后程序停止,第二个promise不会进入回调
Promise.race([winnerPromise, loserPromise]).then(function (value) {
    console.log(value);    // => \'this is winner\'
});

我们可以将刚才的 delayPromise 和其它promise对象一起放到 Promise.race 中来是实现简单的超时机制。

//simple-timeout-promise.js
function delayPromise(ms) {
    return new Promise(function (resolve) {
        setTimeout(resolve, ms);
    });
}
function timeoutPromise(promise, ms) {
    var timeout = delayPromise(ms).then(function () {
            throw new Error(\'Operation timed out after \' + ms + \' ms\');
        });
    return Promise.race([promise, timeout]);//返回taskPromise和timeoutPromise组成的Promise.race
}

定制TimeoutError对象

//TimeoutError
class TimeoutError extends Error{
  constructor(msg){
    super(msg)
  }
}

实例

class TimeoutError extends Error{
  constructor(msg){
    super(msg)
  }
}
function delayPromise(ms) {
    return new Promise(function (resolve) {
        setTimeout(resolve, ms);
    });
}
function timeoutPromise(promise, ms) {
    var timeout = delayPromise(ms).then(function () {
            throw new TimeOutError(\'Operation timed out after \' + ms + \' ms\');
        });
    return Promise.race([promise, timeout]);
}
// 运行示例
var taskPromise = new Promise(function(resolve){
    // 随便一些什么处理
    var delay = Math.random() * 2000;
    setTimeout(function(){
        resolve(delay + "ms");
    }, delay);
});
timeoutPromise(taskPromise, 1000).then(function(value){
    console.log("taskPromise在规定时间内结束 : " + value);
}).catch(function(error){
    if(error instance of TimeoutError){
         console.log("发生超时", error);
    }   
});

通过超时取消XHR操作

到这里,我想各位读者都已经对如何使用Promise来取消一个XHR请求都有一些思路了吧。

取消XHR操作本身的话并不难,只需要调用 XMLHttpRequest 对象的 abort() 方法就可以了。

为了能在外部调用 abort() 方法,我们先对之前本节出现的 getURL 进行简单的扩展,cancelableXHR 方法除了返回一个包装了XHR的promise对象之外,还返回了一个用于取消该XHR请求的abort方法。

//delay-race-cancel.js
function cancelableXHR(URL) {
    var req = new XMLHttpRequest();
    var promise = new Promise(function (resolve, reject) {
            req.open(\'GET\', URL, true);
            req.onload = function () {
                if (req.status === 200) {
                    resolve(req.responseText);
                } else {
                    reject(new Error(req.statusText));
                }
            };
            req.onerror = function () {
                reject(new Error(req.statusText));
            };
            req.onabort = function () {
                reject(new Error(\'abort this request\'));
            };
            req.send();
        });
    var abort = function () {
        // 如果request还没有结束的话就执行abort
        // https://developer.mozilla.org/en/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest
        if (req.readyState !== XMLHttpRequest.UNSENT) {
            req.abort();
        }
    };
    return {
        promise: promise,
        abort: abort
    };
}

在这些问题都明了之后,剩下只需要进行Promise处理的流程进行编码即可。大体的流程就像下面这样。

通过 cancelableXHR 方法取得包装了XHR的promise对象和取消该XHR请求的方法

在 timeoutPromise 方法中通过 Promise.race 让XHR的包装promise和超时用promise进行竞争。

XHR在超时前返回结果的话

和正常的promise一样,通过 then 返回请求结果
发生超时的时候

抛出 throw TimeoutError 异常并被 catch

catch的错误对象如果是 TimeoutError 类型的话,则调用 abort 方法取消XHR请求

将上面的步骤总结一下的话,代码如下所示。

//delay-race-cancel-play.js
class TimeoutError extends Error{
  constructor(msg){
    super(msg)
  }
}
function delayPromise(ms) {
    return new Promise(function (resolve) {
        setTimeout(resolve, ms);
    });
}
function timeoutPromise(promise, ms) {
    var timeout = delayPromise(ms).then(function () {
            return Promise.reject(new TimeoutError(\'Operation timed out after \' + ms + \' ms\'));
        });
    return Promise.race([promise, timeout]);
}
function cancelableXHR(URL) {
    var req = new XMLHttpRequest();
    var promise = new Promise(function (resolve, reject) {
            req.open(\'GET\', URL, true);
            req.onload = function () {
                if (req.status === 200) {
                    resolve(req.responseText);
                } else {
                    reject(new Error(req.statusText));
                }
            };
            req.onerror = function () {
                reject(new Error(req.statusText));
            };
            req.onabort = function () {
                reject(new Error(\'abort this request\'));
            };
            req.send();
        });
    var abort = function () {
        // 如果request还没有结束的话就执行abort
        // https://developer.mozilla.org/en/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest
        if (req.readyState !== XMLHttpRequest.UNSENT) {
            req.abort();
        }
    };
    return {
        promise: promise,
        abort: abort
    };
}
var object = cancelableXHR(\'http://httpbin.org/get\');
// main
timeoutPromise(object.promise, 1000).then(function (contents) {
    console.log(\'Contents\', contents);
}).catch(function (error) {
    if (error instanceof TimeoutError) {
        object.abort();
        return console.log(error);
    }
    console.log(\'XHR Error :\', error);
});

上面的代码就通过在一定的时间内变为解决状态的promise对象实现了超时处理。

以上是关于使用Promise.race实现超时机制取消XHR请求的主要内容,如果未能解决你的问题,请参考以下文章

前端调用后端接口 超时处理 Promise.race() 应用

[RN] React Native Fetch请求设置超时

在promise中超时函数的最佳一般做法是啥[关闭]

Promise.all和Promise.race的区别和使用

Promise.all和Promise.race区别,和使用场景

如何使用并区分Promise.all和Promise.race?