js 链式调用中包含异步函数,如何按顺序让前面的函数执行完后再执行后面的函数?

例如:
test.getData().createDiv();

getData() 里面有 promise 对象去取后端的数据,我想要 getData() 执行完毕,拿到数据之后再执行createDiv()

该怎么写呢,没有思路

阅读 2.6k
3 个回答

最简单粗暴的写法

const test = {
    getData() {
        const promise = new Promise(resolve => setTimeout(resolve, 3000))
        return {
            createDiv() {
                promise.then(() => {
                    console.log('createDiv在promise结束之后执行')
                })
            }
        }
    }
}

大致步骤:

  1. getData 被调用的时候生成一个 Symbol;
  2. createDiv 被调用的时候,如果察觉到 Symbol ,就知道自己来得不是时候,转而注册成一个任务等待执行;
  3. getData中的异步过程结束,拿到结果,自去寻找由 createDiv 注册的任务执行,并给予一定暗示;
  4. 作为任务执行的 createDiv 看到暗示,则同步执行。

例:

class Test{
    // 等待执行的 createDiv 任务们
    #createDivPlans = new Map();

    // 这个标志表明:当前需要注册一个 createDiv
    #nextCreateSymbol;

    getData(){
        const symbol = Symbol();
        this.#nextCreateSymbol = symbol;

        // 用 setTimeout 代替异步过程
        setTimeout(() => {
            // result 代表异步过程的结果
            const result = Math.random();
            console.log('result we\'ve got:', result);
            if(this.#createDivPlans.has(symbol)){
                // 获取到由 createDiv 注册的任务,并执行
                // 额外加参数 symbol 确保 createDiv 不受其他注册事项干扰
                this.#createDivPlans.get(symbol)(symbol, result);
            }
        }, 1e3);

        return this;
    }

    createDiv(symbol, result){
        // 确定自己是不是在被注册的任务中
        if(typeof symbol === 'symbol' && this.#createDivPlans.has(symbol)){
            // createDiv 原有的逻辑在这里,比如新建并插入 DOM 元素
            console.log('result is now:', result);
            return this
        }

        // 目前需要注册一个 createDiv ,而非直接调用
        if(this.#nextCreateSymbol){
            // 注册一个任务,这个任务会在上面的 setTimeout 回调里
            // 调用
            this.#createDivPlans.set(
                this.#nextCreateSymbol,
                (...args) => {
                    this.createDiv(...args, this.#nextCreateSymbol);
                }
            )
            this.#nextCreateSymbol = undefined;
            return this
        }
    }
}

代价略大了点,如果还需要后面的接口继续链式执行且逻辑链一致的话会很壮观,因为异步是有“感染性”的。

改成Builder最后加个build或者commit不行么?

playground

class TaskBuilder {
  private tasks: Promise<void>[] = [];
  private someData: any[] = [];

  private someAction = async (message: string) => {
    const received = await Promise.resolve(`hello ${message}`);
    // any side effect goes here
    this.someData.push(received);
  };

  doSomeAction(message: string) {
    console.log(message, 'requiring message')
    this.tasks.push(this.someAction(message));
    console.log(message, 'message required')
    return this;
  }

  async build() {
    this.tasks.forEach(async (task) => await task);
    return { data: this.someData };
  }
}
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题