同一个Promise多次调用如何同步执行?

const getDetail = ()=>{
  return Promise((resolve)=>{
     setTimeout(()=>{
       // 获取detail,时间不确定
       resolve(detail);
     },Math.Random(5000))
  })
}
const addDetail = ()=>{
  getDetail().then((detail)=>{
     // 增加detail
  })
}
<Button onClick={addDetail}>getDetail</Button>

当我连续快速点击Button时,Promise结束增加detail时,由于时间不确定,最后detail的顺序可能不是点击的顺序。

现在想保证顺序,如何实现同一个Promise,一个结束才执行另一个。

场景不用太在意,只是举例想问 如何实现同一个Promise,一个结束才执行另一个。
阅读 6.9k
6 个回答
const taskQueue = []
let pending = false
const getDetail = ()=>{
  return Promise((resolve)=>{
     setTimeout(()=>{
       // 获取detail,时间不确定
       resolve(detail);
     },Math.Random(5000))
  })
}

const performWorkIfNeeded = () => {
  if(taskQueue.length && !pending) {
    pending = true
    const [task, params] = taskQueue.shift()
    task(params)
    .then(() => {
      pending = false
      performWorkIfNeeded()
    })
  }
}
const addDetail = (params)=>{
  taskQueue.push([getDetail, params])
  
  performWorkIfNeeded()
}
<Button onClick={addDetail}>getDetail</Button>

可以自己维护一个 Promise 队列,每次取一个,执行完以后 finally 回调再取下一个。

不想自己写、或者觉得自己写的健壮性不好的话可以用别人封装好的库,比如 p-queue 之类的(npmjs.com 上自己搜,有很多)。

P.S. 你这不叫同一个 Promise,而是调用了同一个方法、每次调用返回了一个新的 Promise,只不过执行的代码相同而已。

<button>click</button>
        <script>
            let p = Promise.resolve();
            function send() {
                return new Promise(resolve => {
                    const dur = Math.random()*1e4;
                    console.log(`插入${dur}`);
                    setTimeout(resolve, dur, dur);
                }).then(data => {
                    console.log(`执行${data}`);
                })
            }
            function clickHandle() {
                p = p.then(data => {
                    return send();
                })
            }
            document.querySelector('button').addEventListener('click', clickHandle, false);
        </script>

这样试试

新手上路,请多包涵

每次触发生成对应的index(递增),异步返回的结果放入result[index] 中,即可约束获取到数据的顺序

const getDetail = () => {
  return Promise((resolve) => {
    setTimeout(() => {
      // 获取detail,时间不确定
      resolve("detail");
    }, Math.Random(5000));
  });
};

class Scheduler {
  constructor() {
    this.index = 0
    this.results = []
  }
  async add(cb) {
    const i = this.index++
    const result = await cb()
    this.result[i] = result
    // 最后一个结果返回后,才一次性打印
    if (this.results.length === this.index) {
      console.log(this.results)
    }
  }
}

const scheduler = new Scheduler()
const addDetail = ()=>{
  scheduler.add(getDetail)
}

应该可行:



/**
 * @method asyncQueueGenerator 异步任务队列函数生成
 * @param {function(...[*]): Promise<Any>} func - 需要队列化处理的函数
 * @returns {function(...[*]): Promise<unknown>}
 */
const asyncQueueGenerator = func => {
    const taskQueue = [];
    let processing = false;

    const taskProcessor = async () => {
        if(processing || !taskQueue.length) return;
        processing = true;
        const [resolve, reject, args, func] = taskQueue.shift();
        try {
            resolve(await func(...args));
        } catch (err) {
            reject(err);
        } finally {
            processing = false
            // 递归处理直到任务队列为空
            taskProcessor();
        }
    }

    return function(...args) {
        return new Promise((resolve, reject) => {
            taskQueue.push([resolve, reject, args, func.bind(this)]);
            // 启动任务处理
            taskProcessor();
        });
    }
}

跟防抖、节流函数的使用一样,候把函数作为参数执行一遍,返回的结果就自带队列属性了:

const getDetail = asyncQueueGenerator(() => {
    return new Promise((resolve)=>{
        setTimeout(()=>{
            resolve(detail);
        }, Math.Random(5000))
    })
});
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题