Skip to content
This repository was archived by the owner on Aug 7, 2024. It is now read-only.
This repository was archived by the owner on Aug 7, 2024. It is now read-only.

LazyMan 有几样写法,你知道么? #36

Open
@fi3ework

Description

@fi3ework

题目

实现一个 LazyMan,可以按照以下方式调用:

  • LazyMan("Hank")

    输出:

    Hi! This is Hank!

  • LazyMan("Hank").sleep(10).eat("dinner")

    输出:

    Hi! This is Hank!

    等待10秒..

    Wake up after 10

    Eat dinner

  • LazyMan("Hank").eat("dinner").eat("supper")

    输出:

    Hi This is Hank!

    Eat dinner

    Eat supper

  • LazyMan("Hank").sleepFirst(5).eat("supper")

    等待5秒

    Wake up after 5

    Hi This is Hank!

    Eat supper

实现

1. callback

纯 callback 实现, 每个注册的事件的最后会调用对象队列中的下一个事件。

class LazyMan {
  constructor(name) {
    this.name = name
    this.sayName = this.sayName.bind(this)
    this.next = this.next.bind(this)
    this.queue = [this.sayName]
    setTimeout(this.next, 0)
  }

  callByOrder(queue) {
    let sequence = Promise.resolve()
    this.queue.forEach(item => {
      sequence = sequence.then(item)
    })
  }
  
  next(){
  	const currTask = this.queue.shift()
    currTask && currTask()
  }

  sayName() {
    console.log(`Hi! this is ${this.name}!`)
    this.next()
  }

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

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

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

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

2. Promise

手工在每次方法执行后通过 then 调整 Promise 链的序列,缺点是因为 sleepFirst 要强行插入 Promise 链的第一位,要单独抽象出一部分逻辑来前置它的 Promise。

class LazyMan {
  constructor(name) {
    this.name = name
    this._preSleepTime = 0
    this.sayName = this.sayName.bind(this)
    this.p = Promise.resolve().then(() => {
      if (this._preSleepTime > 0) {
        return this.holdOn(this._preSleepTime)
      }
    }).then(this.sayName)
  }

  sayName() {
    console.log(`Hi! this is ${this.name}!`)
  }

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

  sleep(time) {
    this.p = this.p.then(
      () => this.holdOn(time)
    )
    return this
  }

  eat(meal) {
    this.p = this.p.then(() => {
      console.log(`eat ${meal}`)
    })
    return this
  }

  sleepFirst(time) {
    this._preSleepTime = time
    return this
  }
}

3. Promise + 队列

在对象内部维护一个队列,让所有的事件都变成异步的,然后在内部通过 Promise.resolve.then() 来将队列的执行启动推迟到下一个 eventloop,这样做逻辑更清楚,所有事件都由队列来管理。

class LazyMan {
  constructor(name) {
    this.name = name
    this.sayName = this.sayName.bind(this)
    this.queue = [this.sayName]
    Promise.resolve().then(() => this.callByOrder(this.queue))
  }

  callByOrder(queue) {
    let sequence = Promise.resolve()
    this.queue.forEach(item => {
      sequence = sequence.then(item)
    })
  }

  sayName() {
    return new Promise((resolve) => {
      console.log(`Hi! this is ${this.name}!`)
      resolve()
    })
  }

  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
  }

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

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

4. Promise + async

基本思路与第 2 种方法相同,不同的地方只在于使用了 async 来顺序执行队列。

class LazyMan {
  constructor(name) {
    this.name = name
    this.sayName = this.sayName.bind(this)
    this.queue = [this.sayName]
    setTimeout(async () => {
      for (let todo of this.queue) {
        await todo()
      }
    }, 0)
  }

  callByOrder(queue) {
    let sequence = Promise.resolve()
    this.queue.forEach(item => {
      sequence = sequence.then(item)
    })
  }

  sayName() {
    return new Promise((resolve) => {
      console.log(`Hi! this is ${this.name}!`)
      resolve()
    })
  }

  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
  }

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

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

TODO

  • yield 的实现版本

总结

整个过程其实还挺有意思的,多写几种方法挺练 Promise 和整个异步调用的思维的..

Activity

EmiyaYang

EmiyaYang commented on Mar 20, 2020

@EmiyaYang

学习了

EmilyYoung71415

EmilyYoung71415 commented on Apr 24, 2020

@EmilyYoung71415

可以把任务看成三类:sleepFirst(微任务)、sayname(constructor任务)、eat&sleep(宏任务)
整个过程就是: 先串行执行微任务,再执行constructor任务,最后串行执行宏任务

class LazyMan {
    constructor(name) {
        this.name = name;
        this.microTaskList = []; // 微任务
        this.macroTaskList = []; // 宏任务

        // Promise.resolve()使得先注册回调 再执行constructor
        Promise.resolve()
        .then(() => this.runInOrder(this.microTaskList))
        .then(() => this.sayHello())
        .then(() => this.runInOrder(this.macroTaskList));
    }
    sleepFirst(time) {
        this.taskListAdd({type: 'micro', funcType: 'sleepFirst', params: {time}});
        return this;
    }
    sleep(time) {
        this.taskListAdd({type: 'macro', funcType: 'sleep', params: {time}});
        return this;
    }
    eat(food) {
        this.taskListAdd({type: 'macro', funcType: 'eat', params: {food}});
        return this;
    }
    sayHello() {
        console.log(`Hi This is ${this.name}`);
    }
    runInOrder(promiseArr) {
        return promiseArr.reduce(
            (prevPromise, nextPromise) => prevPromise.then(() => nextPromise()),
            Promise.resolve()
        );
    }
    taskListAdd({type, funcType, params}) {
        let func = () => {};
        switch (funcType) {
            case 'sleep':
            case 'sleepFirst':
                func = () => new Promise(resolve => {
                    setTimeout(() => {
                        console.log(`Wake up after ${params.time}`);
                        resolve();
                    }, params.time * 1000);
                });
                break;
            case 'eat':
                func = () => new Promise(resolve => {
                    console.log(`Eat ${params.food}~`);
                    resolve();
                });
                break;
            default:
                break;
        }

        this[`${type}TaskList`].push(func);
    }
}
willxiao90

willxiao90 commented on Oct 16, 2020

@willxiao90

这是我想到的版本,看到这个题目,我没有自己去写任务队列,而是使用了系统级别的宏任务和微任务

class LazyMan {
  constructor(name) {
    this.name = name;
    setTimeout(() => {
      console.log("Hi! This is " + name);
    }, 0);
  }

  sleep(seconds) {
    const delay = seconds * 1000;
    const time = Date.now();
    while (Date.now() - time < delay) {
      // hu lu lu ~~
    }
    setTimeout(() => {
      console.log("wake up after " + seconds);
    }, 0);
    return this;
  }

  eat(something) {
    setTimeout(() => {
      console.log("eat " + something);
    }, 0);
    return this;
  }

  sleepFirst(seconds) {
    new Promise((resolve) => {
      const delay = seconds * 1000;
      const time = Date.now();
      while (Date.now() - time < delay) {
        // hu lu lu ~~
      }
      resolve();
    }).then(() => {
      console.log("wake up after " + seconds);
    });
    return this;
  }
}

function lazyMan(name) {
  return new LazyMan(name);
}

lazyMan("Hank").sleep(2).eat("dinner").sleepFirst(3);
think2011

think2011 commented on Oct 16, 2020

@think2011
function lazyMan (name) {
    const tasks = []
    const methods = {
        say(name) {
            tasks.push(() => console.log(`Hi! This is ${name}`))
            return this
        },
        eat(food) {
            tasks.push(() => console.log(`Eat ${food}`))
            return this
        }, 
        sleepFirst(time) {
            tasks.unshift(() => new Promise(resolve => setTimeout(resolve, time * 1000)))
            return this
        },  
        sleep(time) {
            tasks.push(() => new Promise(resolve => setTimeout(resolve, time * 1000)))
            return this;
        }
    }

    setTimeout(function run() {
        if(!tasks.length) return
        Promise.resolve(tasks.shift()()).then(run)
    }, 0)
    methods.say(name)

    return methods
}


// lazyMan('think2011').sleep(3).eat('supper')
// lazyMan('think2011').eat('dinner').eat('supper')
lazyMan('think2011').sleepFirst(3).eat('supper')

我来一个不使用 class 的

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @think2011@willxiao90@fi3ework@EmilyYoung71415@EmiyaYang

        Issue actions

          LazyMan 有几样写法,你知道么? · Issue #36 · fi3ework/blog