fi3ework / blog

📝
861 stars 51 forks source link

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

Open fi3ework opened 6 years ago

fi3ework commented 6 years ago

题目

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

实现

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

总结

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

EmiyaYang commented 4 years ago

学习了

EmilyYoung71415 commented 4 years ago

可以把任务看成三类: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 commented 4 years ago

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

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 commented 4 years ago

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 的