Skip to content

如何正确的在vue中"同步"使用axios

起因

作为经常经常绑定在一起的Vueaxios,绝大多数人都使用过。这次群里反馈一个有趣的场景:首先ajax请求一个树列表的第一层,然后根据列表的每一项的id去请求下一级的数据赋给当前对象的children;先不讨论这个逻辑设计得合不合理,毕竟存在即合理的。

代码实现类似这样:

async load() {
    let data = await axios({
        methods: "get",
        url: "./json/list.json"
    })
    data = data.data.data;
    data.forEach(async value => {
        let res = await axios({
            methods: "get",
            url: "./json/scroe.json?id=" + value.id
        })
        value.scroe = res.data.data;
    })
    this.list = data;
}

渲染列表的时候,scroe渲染不出来。

Axios 是一个基于 promise 的 HTTP 库

首先虽然标题是同步使用的axios但是明确得告诉大家,这里得axios请求是异步的,毕竟人家是基于axios的HTTP库,为什么会提供给你一个同步的设置呢,所以不要想找到一个类似jqasync:fasle的配置是没有的。

但是呢,你非要,百度还是会告诉你一个答案滴:

async ()=>{
   await axios.get(url,params);
}

这个答案是没有问题的,但是需要注意一点是,async/await是让async修饰的函数内部await修饰的promise转化为同步流程执行。

开启分析模式

首先这一堆的async/await确实唬人,所有的请求都同步了,为什么还不行?

javascript
async load() {
    let data = await axios({
        methods: "get",
        url: "./json/list.json"
    })
    data = data.data.data;
    data.forEach(async value => {
        let res = await axios({
            methods: "get",
            url: "./json/scroe.json?id=" + value.id
        })
        value.scroe = res.data.data;
    })
    this.list = data;
}

这段代码,只要赋值的data有值,那么list就可以渲染出结果。所以怀疑这里有两个异步。

问题出在了forEach上 - forEach是一个同步循环调用,它不会等待async函数内的await完成,会直接继续下一次循环。

forEach

javascript的数组循环方法:

[1,2,3].forEach(()=>{})

内部实现类似:

javascript
Array.prototype.forEach = function(Fn){
    var _this = this,
        len = _this.length,
        params2 = arguments || window;
     for( var i = 0 ; i < len; i++ ){
          Fn.call(params2,_this[i],i,_this);
      }
}

虽然async/await能保证传入的函数体内顺序执行,但是forEach这个启动器是一个同步循环调用,不会等待await的异步完成,继续执行下面一次循环。

解决方案

方案一:使用$set

javascript
this.$set(value,'scroe',res.data.data)

方案二:使用for循环替代forEach

javascript
async forEachLoadData(){
    for( var i = 0 ,len = data.length; i < len; i++ ){
        let res = await axios({
            methods: "get",
            url: "./json/scroe.json?id=" + data[i].id
        })
        data[i].scroe = res.data.data;
    }
}

方案三:使用reduce实现顺序执行

javascript
async function load() {
    let data = await [1, 2].reduce(async (p, c, index) => {
        let data = await p;
        return new Promise(resolve=>{
            let time = index == 0 ? 5000 : 1000
            setTimeout(()=>{
                resolve(index )
            },time)
        })
    }, Promise.resolve(0));
    console.log('data',data)
}

原生数组方法实现

通过上面例子,我们可以发现,我们其实都是通过awaitpromise交叉来改变函数体内部的逻辑。

类似可以直接使用Promise.all方法处理多个promise

需要注意的是,需要在回调中返回一个promise

关于reduce的更多内容可以查看下一张,reduce和中间件。