并行执行

或许,可能有遇到过这样的需求:

  • 一个接口的参数是由其他三个接口的返回值组合而成,因此这个接口必须要保证在另外三个接口完成之后才能被调用;
  • 在echarts的图表中,一个图表中可以有好几组数据,每组数据都由一个接口返回,根据echarts使用规则,必须得这几个接口都请求完成之后才渲染图,比如下面这个,有四组数据在同一个echarts表里:

以上两种情况,都是需要在某几个接口请求完毕之后再执行另一个接口。

这个涉及到并行执行的概念,Promise.all() 刚好可以解决这个问题。

上代码:

// 仿一个ajax请求,传入一个数作为时间参数,会在对应时间之后返回一个结果
function ajax(time) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(`完成了-${time}`)
        }, time)
    })
}

// 三个ajax执行完后,才回执行then里的代码
Promise.all([
    ajax(1000),
    ajax(1500),
    ajax(2000)
]).then(res => {
    console.log(res)
})

Promise.all() 不再做过多讲解,总结一句话就是:

Promise.all()里面的方法都执行完毕后,才会调用then回调函数里面的代码,回调函数返回的值是一个数组,分别存放Promise.all() 里方法的返回信息。

比如,上边代码,会在执行两秒后,返回结果如下:

javascript实现并发锁_回调函数


错误处理

那么,如果Promise.all里的方法有一个出错了该如何处理呢?

这需要针对每一个方法单独做处理,处理后的结果会被Promise.all接收:

function ajax(time) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            // 如果参数为1500,抛出一个错误
            if (time === 1500) {
                reject(new Error('参数不能为1500'))
            }
            resolve(`完成了-${time}`)
        }, time)
    })
}

// 每个请求做catch错误处理
Promise.all([
    ajax(1000).catch(err => console.log(err)),
    ajax(1500).catch(err => console.log(err)),
    ajax(2000).catch(err => console.log(err))
]).then(res => {
    console.log(res)
})

执行结果如下:

javascript实现并发锁_ajax_02


在并发执行的使用中,有一种优雅的写法,就是把所有需要并行执行的接口都写在一个接口里,请求这个接口后,将返回的接口通过循环来并行执行,代码如下:

ajax('/api/urls.json')
	.then(value => {
		const urls = Object.values(value)
		const tasks = urls.map(url => ajax(url))
		return Promise.all(tasks)
	})
	.then(values => {
		console.log(values)
	})



超时处理

先来看另一个方法:Promise.race()

上代码,来看个现象:

function ajax(time) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(`完成了-${time}`)
        }, time)
    })
}

Promise.race([
    ajax(1000),
    ajax(1500),
    ajax(2000)
]).then(res => {
    console.log(res)
})

和 Promise.all() 方法高度一致,只是将 all 换成了 race。

race 为竞赛之意,意思是说,Promise.race() 里的方法,只要有一个执行完成了,就会调用then方法里的回调函数,回调函数接收的是最早执行完的那个方法的返回信息。

所以如上代码会在执行一秒会返回:

javascript实现并发锁_javascript实现并发锁_03


那么这个方法有什么作用呢?它常常用在接口的超时处理上。

有一个接口,如果5秒内没返回结果,就报错,不再请求,可参考如下示例:

// 要7秒才能执行完
function ajax1() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('7秒后我才完成')
        }, 7000)
    })
}

// 5秒后,抛出一个错误
function timeout() {
    return new Promise((resolve, reject) => {
        setTimeout(() => reject(new Error('超过5秒了')), 5000)
    })
}

Promise.race([
    ajax1(),
    timeout()
]).then(res => {
    console.log(res)
})

根据 Promise.race 特性,如果ajax1所请求的接口能在5秒内完成,then里的回调函数会返回请求所得的信息;如果5秒内不能完成,timeout将会执行然后跑出一个错误。

超时处理就完成了。