Bootstrap

多种方式实现 LazyMan

本教程会用多种方式来实现一个 。

先来了解一下什么是 , 方法可以按照以下方式调用:


LazyMan('Hank');
// 输出:
// Hi! This is Hank!

LazyMan('Hank').sleep(3).eat('dinner')
// 输出:
// Hi! This is Hank!
// //等待3秒..
// Wake up after 3
// Eat dinner~

LazyMan('Hank').eat('dinner').eat('supper')
// 输出:
// Hi This is Hank!
// Eat dinner~
// Eat supper~

LazyMan('Hank').sleepFirst(2).eat('dinner').sleep(3).eat('supper')
// 输出:
// //等待2秒..
// Wake up after 2
// Hi This is Hank!
// Eat dinner~
// //等待3秒..
// Wake up after 2
// Eat supper~

// 以此类推

一般思路可以通过 任务队列 来解决 问题,结合 Promise 或者 async 还可以更加优雅的实现。

还可另辟蹊径通过 RxJS 以及其丰富的操作符来解决 问题。

为了保证阅读效果,建议读者边阅读边动手实操,点击可以下载源码。

1、任务队列实现

这种模式类似 中间件模式, 核心是 方法,每当队列中的一个方法执行完都会调用 来执行队列中的另外一个方法,直到全部执行完成。

而构造函数中的 保证了队列开始执行的时间是在下一个事件循环中,从而确保当前的链式调用中的所有行为在调用之前被加载进队列之中。

class _LazyMan {
  queue: any[] = [];
  constructor(name: string) {
    this.sayName(name);

    setTimeout(() => {
      this.next();
    })
  }

  next() {
    const fn = this.queue.shift();
    fn && fn();
  }

  _holdOn(time) {
    return () => {
      setTimeout(() => {
        console.log(`Wake up after ${time} second`)
        this.next()
      }, time * 1000)
    }
  }

  sayName(name) {
    const fn = () => {
      console.log(`Hi! This is ${name}!`);
      this.next();
    }
    this.queue.push(fn);
  }

  sleep(time: number) {
    this.queue.push(this._holdOn(time));
    return this;
  }

  eat(some: string) {
    const fn = () => {
      console.log(`Eat ${some}~`);
      this.next();
    }
    this.queue.push(fn);
    return this;
  }

  sleepFirst(time: number) {
    this.queue.unshift(this._holdOn(time));
    return this;
  }
}

const LazyMan = (name: string) => new _LazyMan(name);

LazyMan('Hank').sleepFirst(2).eat('dinner').sleep(3).eat('supper');

运行查看效果:

yarn demo01

2、任务队列 + `Promise` 实现

使用 来实现很讨巧,可以用 的天然异步来替代 **方法一** 中的 调用方式

class _LazyMan {
  queue: any[] = [];
  name: string;
  constructor(name) {
    this.name = name
    this.sayName(name)
    Promise.resolve().then(() => {
      let sequence = Promise.resolve()
      this.queue.forEach(item => {
        sequence = sequence.then(item)
      })
    })
  }

  sayName(name) {
    this.queue.push(() => {
      console.log(`Hi! this is ${name}!`)
    })
    return this
  }

  eat(meal) {
    this.queue.push(() => {
      console.log(`eat ${meal}`)
    })
    return this
  }

  _holdOn(time) {
    return () => new Promise(resolve => {
      setTimeout(() => {
        console.log(`Wake up after ${time} second`)
        resolve()
      }, time * 1000)
    })
  }

  sleep(time) {
    this.queue.push(this._holdOn(time))
    return this
  }

  sleepFirst(time) {
    this.queue.unshift(this._holdOn(time))
    return this
  }
}

const LazyMan = (name: string) => new _LazyMan(name);

LazyMan('Hank').sleepFirst(2).eat('dinner').sleep(3).eat('supper');

export { };

// 参考文章:https://github.com/fi3ework/blog/issues/36

运行查看效果:

yarn demo02

3、任务队列 + `async` 实现

这种实现方式和 方法二 的实现方式差别不大,只不过用 的方式来实现更加优雅

class _LazyMan {
  queue: any[] = [];
  name: string;
  constructor(name) {
    this.name = name
    this.sayName(name)
    setTimeout(async () => {
      for (let todo of this.queue) {
        await todo()
      }
      // 下面这种写法也可以
      // for await (let todo of this.queue) {
      //   todo()
      // }
    })
  }

  sayName(name) {
    this.queue.push(() => {
      console.log(`Hi! this is ${name}!`)
    })
    return this
  }

  eat(meal) {
    this.queue.push(() => {
      console.log(`eat ${meal}`)
    })
    return this
  }

  _holdOn(time) {
    return () => new Promise(resolve => {
      setTimeout(() => {
        console.log(`Wake up after ${time} second`)
        resolve()
      }, time * 1000)
    })
  }

  sleep(time) {
    this.queue.push(this._holdOn(time))
    return this
  }

  sleepFirst(time) {
    this.queue.unshift(this._holdOn(time))
    return this
  }
}

const LazyMan = (name: string) => new _LazyMan(name);

LazyMan('Hank').sleepFirst(2).eat('dinner').sleep(3).eat('supper');

export { };

// 参考文章:https://github.com/fi3ework/blog/issues/36

运行查看效果:

yarn demo03

4、RxJS实现

基于 RxJS 的实现方式让人看起来耳目一新,不过需要读者朋友拥有良好的 RxJS 基础。

RxJS 对异步的天然支持以及丰富的操作符让这一切变的非常简单。

在实现 中,核心是 操作符,他会收集所有 observables,并在当前一个完成时订阅下一个。

import { from, of } from 'rxjs';
import { map, concatAll, delay } from 'rxjs/operators';

class _LazyMan {
  tasks: any[] = [];

  constructor(name: string) {
    this.sayName(name);

    setTimeout(() => {
      from(this.tasks).pipe(
        map(item => {
          if (item.timeout) {
            return of(item).pipe(delay(item.timeout))
          }
          return of(item)
        }),
        concatAll()
      ).subscribe(res => res.fn())
    })
  }

  sayName(name) {
    this.tasks.push({
      fn: () => console.log(`Hi! This is ${name}!`)
    });
  }

  sleep(time: number) {
    this.tasks.push({
      timeout: time * 1000,
      fn: () => console.log(`Wake up after ${time} second`)
    });
    return this;
  }

  eat(some: string) {
    this.tasks.push({
      fn: () => console.log(`Eat ${some}~`)
    });
    return this;
  }

  sleepFirst(time: number) {
    this.tasks.unshift({
      timeout: time * 1000,
      fn: () => console.log(`Wake up after ${time} second`)
    });
    return this;
  }
}

const LazyMan = (name: string) => new _LazyMan(name);

LazyMan('Hank').sleepFirst(2).eat('dinner').sleep(3).eat('supper');

运行查看效果:

yarn demo04

上面例子的完整代码可以查看,如果觉得写的不错,可以给笔者一个 star,感谢阅读。

相关链接