谢谢大家,想请大家帮忙看看这个封装的请求怎样去修改?

export const request = (options = {}) => {
    //异步封装接口,使用Promise处理异步请求
    return new Promise((resolve, reject) => {
    
            let rd3_key = uni.getStorageSync('rd3_key') ? uni.getStorageSync('rd3_key') : ''
            
            // 发送请求
            uni.request({
                url: baseUrl + options.url || '', //接收请求的API
                method: options.method || 'GET', //接收请求的方式,如果不传默认为GET
                data: options.data || {}, //接收请求的data,不传默认为空
                data: {
                    ...options.data,
                    rd3_key
                },
                header: {
                    'content-type': 'application/x-www-form-urlencoded', //修改此处即可
                }
            }).then(data => {
                let [err, res] = data;
                // if(res.data.code == 4001){
                //     post_login()
                // }
                
                if(res.data.code == 4001){
                    // return post_login().then(resolve, reject)
                    post_login()
                    
                }
                
                resolve(res);
            }).catch(error => {
                
                reject(error);
            })
        
    
    })
}

下面这段

  if(res.data.code == 4001){
                    // return post_login().then(resolve, reject)
                    post_login()
                    
                }

是登录失效后 接口请求返回4001,这时候会调用post_login() 重新获取最新的登录态完成静默登录,这个过程没什么问题,但是我写的onload事件里的那些业务接口却不会重新再发起请求了,请问这种情况 怎样统一处理下?

阅读 1.6k
2 个回答

在解决你本来要问的问题之前,先说一个代码现在的问题。

看代码处理方式,uni.request() 返回的显然是一个 Promise 或者 Promise Like。无所谓,都不需要再封一层 Promise。按原来的逻辑,处理成

// 去掉了业务逻辑部分
return ui.request().then(([res, data]) => data);

如果使用 async/await 的话,看起来逻辑会更清晰

// 仅示意,不含参数和业务逻辑
request = async () => {
    const [err, res] = await uni.reqeust();
    return res;
}

接下来处理问题本身。问题本身要处理的是两个分支:

  1. 如果是 post_login 调用 request,不处理 4001 之类的错误(这个在问题中没明确描述)
  2. 如果是其他调用 request 处理 4001 并尝试自动登录

先说第 2 个,如果要自动登录,那么这个 request 的逻辑大概是这样:

async function request() {
    let [err, res] = await businessRequest();
    if (res.data.code === 4001) {
        post_login();
        ([err, res] = await businessRequest()); // 再试一次
    }
    return res;
}

这段代码为了在 post_login() 后重试,有可能处理两次业务逻辑,所以需要封装到一个函数中以便复用,也就是上面示例代码中需要封装到 businessRequest() 中的内容,显然 request 中去掉尝试登录的逻辑,就是要封装的内容。所以这里有两种处理方式

  1. request 中移除尝试登录的代码,把这个事情交给一个新的外层封装 requestWithLogin 去干
  2. 直接在 request 中封装逻辑部分,像上面的示例那样(可以封装成一个局部函数,也可以是一个同级的外部函数)

这里按 2 的方式来处理,单独写一个同级函数。要按 1 或局部函数的处理,只需要少量改动

async function requestDirectly(options = {}) {
    // 就是原来的业务,简述为代码如下:
    const [, res] = await uni.request();
}

export async function request(options = {}) {
    let [, res] = await requestDirectly(options);
    if (res.data.code === 4001) {
        post_login();
        ([, res] = await requestDirectly(options));
    }
    return res;
}

到这一步,基本上已经解决问题了,但其实还有一个问题:如果在登录失效的情况下,同时发起多个请求,会怎么样?

同时发起多个请求,那这几个请求可能都需要重新登录,于是都会去尝试登录 —— 但实际只,只需要有一个去尝试登录就可以了。

怎么处理呢?加一个锁变量,检查到锁变量就等待一定时间直到解锁,然后再尝试登录(失败则直接返回)。这个逻辑可以写在 post_login

let loginLock = false;

async function waitLoginLock() {
    return new Promise(resolve => {
        if (!loginLock) { resolve(); }
        const timer = setInterval(() => {
            if (!loginLock) {
                clearInterval(timer);
                resolve();
            }
            // TODO 这里最好加一个超时判断
        }, 200);
    })
}

async function post_login() {
    if (loginLock) {
        await waitLoginLock();
        return;
    }
    loginLock = true;
    // TODO 这里是登录的逻辑
    loginLock = false;
}

最后要处理一个遗留的问题,如果在 post_login 中调用 request 该怎么避免去尝试登录(否则可能造成无限递归)?

其实很简单……post_login 中去调用没尝试登录的那个 requestDirectly 就可以了。

不过 requestDirectly 是没 export 的(可能是为了保护),所以可以给 request 加一个参数(在 options 里或外都可以),根据这个参数来判断是否需要去尝试登录。

区分好哪些接口是需要要在登陆后马上进行请求的,可以将这些请求和登录请求包装到一个函数中。这样如果你登录状态失效后,直接调用这个函数。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题