实现链式延迟调用 - person.eat().sleep(2).eat()

2,301 阅读2分钟

从一道面试题说起,实现链式延迟调用。

思路一 - 队列

这道题如果是先实现person.add(eat).add(sleep).run() ,其实很容易我们会知道要做一个队列,然后通过同步化异步代码和变量保证前一个任务执行完毕后执行下一个任务,这时候启动项在 run()。

然后我们在进一步实现person.eat().sleep().run() ,eat 和 sleep 形式改变一下。

最后实现person.eat().sleep() ,关键点就是让他们自己执行完之后继续执行下一个。我们之后在每次运行之后执行一个 run 函数即可。

实现:

  1. 链式调用,返回 this;
  2. eat 和 sleep 函数功能实现,简单;
  3. 实现等待效果;
function Person() {
  this.queue = [];
  this.excuteLock = false;
}
Person.prototype.eat = function() {
  this.queue.push(
    () =>
      new Promise(res => {
        console.log("eat");
        res();
      })
  );
  this.run();
  return this;
};

Person.prototype.sleep = function(time) {
  this.queue.push(
    () =>
      new Promise(res => {
        setTimeout(() => {
          console.log("sleep");
          res();
          this.run();
        }, time * 1000);
      })
  );
  this.run();
  return this;
};

// 核心
Person.prototype.run = async function() {
  if (this.queue.length > 0 && !this.excuteLock) {
    this.excuteLock = true;
    const task = this.queue.shift();
    await task();
    this.excuteLock = false;
    this.run();
  }
};

const a = new Person();
a.eat()
  .sleep(1)
  .eat()
  .sleep(3)
  .eat();

思路二 - setTimeout 队列

关键点:如何让上一个任务结束之后执行下一个任务。 通过 setTimeout 异步队列的形式,js 会先执行完执行栈中同步任务,然后去访问 setTimeout 异步队列的任务,执行完一个剥离一个。启动在于第一个setTimeout 的剥离。

function Person() {
  this.queue = [];
  setTimeout(() => {
    this.then();
  });
}

Person.prototype.eat = function() {
  this.queue.push(() => {
    console.log("eat");
    this.then();
  });

  return this;
};

Person.prototype.sleep = function(time) {
  this.queue.push(() => {
    setTimeout(() => {
      console.log("sleep");
      this.then();
    }, time * 1000);
  });
  return this;
};

// 核心
Person.prototype.then = function() {
  const task = this.queue.shift();
  task && task();
};

const a = new Person();
a.eat()
  .sleep(1)
  .eat()
  .sleep(3)
  .eat();

思路三 - Promise.then

换一个角度,我们就是想实现类似于 then 的效果,采用 promise.then调用思路,构建一个 promise 队列返回。链式调用需要上一个执行完毕才会接着执行,把 eat 接在初始 promise 的 then 中,把 sleep 接在 eat 的 then 中。

function Person() {
  this.queue = new Promise(res => res());
}
Person.prototype.eat = function() {
  this.queue = this.queue.then(() => {
    return new Promise(cres => {
      console.log("eat");
      cres();
    });
  });

  return this;
};

Person.prototype.sleep = function(time) {
  this.queue = this.queue.then(() => {
    return new Promise(cres => {
      setTimeout(() => {
        console.log("sleep");
        cres();
      }, time * 2000);
    });
  });

  return this;
};

const a = new Person();
a.eat()
  .sleep(1)
  .eat()
  .sleep(2)
  .eat();