如何让 promise 顺序执行

500 阅读1分钟

最近遇到这样一个需求,让某个 dom 以一定的延时移动到某个坐标。例如给定以下坐标:

(100, 200)
(100, 300)
(300, 400)

需要每隔 400ms 移动一次位置。由于坐标位置和数量都不固定,因此写 keyframe 不太方便。于是决定用 Promise 来实现,固定延时一段时间并 set 坐标,然后继续延时,继续 set。这个需求可以转换为: 给定多个生成 promise 的函数,如何将这些 promise 按顺序执行。这里介绍两种常用的方式:

基础准备

首先模拟一个 sleep 函数:

function sleep(timeMs){
    return new Promise((resolve, reject) => {
        setTimeout(resolve, timeMs)
    })
}

使用 reduce 实现

function steps(funcs, timeMs){
    funcs
    .map((func) => (() => sleep(timeMs).then(func))) // 将每个函数包装为 sleep(xxx).then(func)
    .reduce((x, y) => x.then(y), Promise.resolve()) 
}

首先将每个函数包装为一个 () => sleep(timeMs).then(func) 函数,然后使用 reduce 让每个 promise 顺序执行,可以看作:

const p1 = () => sleep(timeMs).then(func1)
const p2 = () => sleep(timeMs).then(func2)
const p3 = () => sleep(timeMs).then(func3)

const p4 = p1().then(p2) // p1 resolve 之后执行 func1 并返回 p2
const p5 = p4().then(p3) // p2 resolve 之后执行 func2 之后返回 p3,p3 resolve 执行 func3

使用 await 实现

使用 reduce 实现的方式虽然代码不复杂,但是看起来并不好理解。这个时候就能体现出 await 的优势,采用 await 改写之后就简洁易懂很多:

function steps(funcs, timeMs){
   (async () => {
        for(const func of funcs){
            await sleep(timeMs)
            func()
        }
   })()
}

包装在一个匿名 async 函数之后使用 await 逐个执行即可。