Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

14. 实现一个 LazyMan & 升级实现每个都能返回自己的 then (即asyncQueue) #15

Open
fedono opened this issue Jul 7, 2020 · 4 comments
Labels

Comments

@fedono
Copy link
Owner

fedono commented Jul 7, 2020

问题

实现一个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

解法

对于这个问题,首先创建一个任务队列,然后利用next()函数来控制任务的顺序执行:

核心是队列和事件执行
这里有两个点是要注意的,第一个是在_LazyMan 的时候需要执行next函数,但不能立即执行,而是需要放到 setTimeout 里面去,这样才不会导致在 sleepFirst 的时候,已经把fns中第一个函数执行完了,先执行同步任务,然后再执行异步任务,这样才会把sleepFirst 放到第一个fns 中

第二个是在 eat/sleep 函数中,都是在 setTimeout 中执行 next 函数的

function _LazyMan(name) {
    this.fns = [];
    let self = this;
    const fn = () => {
        setTimeout(() => {
            console.log('Hi! This is ' + name);
            self.next();
        });
    }

    this.fns.push(fn);
    // 如果这里直接使用 self.next(),就无法将 sleepFirst 放到第一位来执行了
    setTimeout(() => {
        self.next();
    }, 0)
}

// 使用 next 来执行下一次的函数
_LazyMan.prototype.next = function() {
    let fn = this.fns.shift();
    fn && fn();
}

_LazyMan.prototype.sleep = function(time) {
    let self = this;
    const fn = () => {
        setTimeout(() => {
            console.log('Wake up after ' + time);
            self.next();
        }, time * 1000);
    }
    this.fns.push(fn);
    return this;
}

_LazyMan.prototype.eat = function(food) {
    let self = this;
    const fn = () => {
        setTimeout(() => {
            console.log('Eat ' + food);
            self.next();
        }, 0);
    }
    this.fns.push(fn);
    return this;
}

// 看到 sleepFirst 就知道要建立队列的概念
_LazyMan.prototype.sleepFirst = function(time) {
    let self = this;
    const fn = () => {
        setTimeout(() => {
            console.log('Wake up after  ' + time);
            self.next();
        }, time * 1000);
    }
    this.fns.unshift(fn);
    return this;
}

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

LazyMan('jack').sleep(2).eat('orange').sleepFirst(3)

参考

@fedono fedono added the JS label Jul 7, 2020
@fedono
Copy link
Owner Author

fedono commented Sep 9, 2023

贴个自己完成的

重点有三个

  1. 首先就是方法放在 prototype 上,要不然拿不到对应的方法
  2. 第二是需要返回 this,要不然无法向下衔接
  3. 第三则是每次运行完当前的方法,则直接执行下一个方法
  4. 第四则是需要使用 setTimeout 来完成队列的形式
function LazyMan(name) {
    return new _LazyMan(name)
}

function _LazyMan(name) {
    this.name = name;
    this.queues = []

    // 这里首先要执行的 hello 
    this.Hello()

    // 这个 setTimeout 很重要,主要是为了 push 完 .sleepFirst/.eat 这些方法到队列中
    setTimeout(() => {
        this.exec()
    })
}

_LazyMan.prototype.Hello = function() {
    this.handler(() => {
        console.log(`Hi This is ${this.name}!`)
    })

    return this;
}

_LazyMan.prototype.handler = function(fn, number = 0, before = false) {
    const cur = () => setTimeout(() => {
        fn()
        this.exec()
    }, number * 1000)

    if (before) {
        this.queues.unshift(cur)
    } else {
        this.queues.push(cur)
    }
}

_LazyMan.prototype.exec = function(){
    const fn = this.queues.shift()
    fn?.()
}

_LazyMan.prototype.sleepFirst = function(time) {
    this.handler(() => {}, time, true)

    return this;
}

_LazyMan.prototype.eat = function(food)  {
    this.handler(() => {
        console.log(`Eat ${food} !`)
    })
    
    return this;
}

@fedono fedono changed the title 14. 实现一个 LazyMan 14. 实现一个 LazyMan & 升级实现每个都能返回自己的 then (即asyncQueue) Sep 13, 2023
@fedono
Copy link
Owner Author

fedono commented Sep 13, 2023

class AutoQueue {
  constructor() {
    this.tasks = [];
    this._pendingPromise = false;
  }

  enqueue(action) {
    return new Promise((resolve, reject) => {
      this.tasks.push({ action, resolve, reject });
      this.dequeue();
    });
  }

  async dequeue() {
    // 这个的主要目的,就是当前有一个正在执行时,下一个 action 就不要执行,等着前一个执行完
    if (this._pendingPromise) return false;

    let item = this.tasks.shift();

    if (!item) return false;

    try {
      this._pendingPromise = true;

      let payload = await item.action(this);

      this._pendingPromise = false;
      item.resolve(payload);
    } catch (e) {
      this._pendingPromise = false;
      item.reject(e);
    } finally {
      this.dequeue();
    }

    return true;
  }
}

// Helper function for 'fake' tasks
// Returned Promise is wrapped! (tasks should not run right after initialization)
let _ =
  ({ ms, ...foo } = {}) =>
  () =>
    new Promise((resolve) => setTimeout(resolve, ms, foo));
// ... create some fake tasks
let p1 = _({ ms: 50, url: '❪𝟭❫', data: { w: 1 } });
let p2 = _({ ms: 20, url: '❪𝟮❫', data: { x: 2 } });
let p3 = _({ ms: 70, url: '❪𝟯❫', data: { y: 3 } });
let p4 = _({ ms: 30, url: '❪𝟰❫', data: { z: 4 } });

const aQueue = new AutoQueue();
const start = performance.now();

aQueue
  .enqueue(p1)
  .then(({ url, data }) => console.log('%s DONE %fms', url, performance.now() - start)); //          = 50
aQueue
  .enqueue(p2)
  .then(({ url, data }) => console.log('%s DONE %fms', url, performance.now() - start)); // 50 + 20  = 70
aQueue
  .enqueue(p3)
  .then(({ url, data }) => console.log('%s DONE %fms', url, performance.now() - start)); // 70 + 70  = 140
aQueue
  .enqueue(p4)
  .then(({ url, data }) => console.log('%s DONE %fms', url, performance.now() - start)); // 140 + 30 = 170

来源 js-async-await-tasks-queue

@fedono
Copy link
Owner Author

fedono commented Sep 18, 2023

贴一个实现的比较好的版本,不用 setTimeout,直接使用 Promise.resolve().then(fn) 的方式

const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms))

/*
interface Laziness {
  sleep: (time: number) => Laziness
  sleepFirst: (time: number) => Laziness
  eat: (food: string) => Laziness
}
*/

/**
 * @param {string} name
 * @param {(log: string) => void} logFn
 * @returns {Laziness}
 */
function LazyMan(name, logFn) {
  const cmds = [['greet', name]]

  const actions = {
    greet: name => logFn(`Hi, I'm ${name}.`),
    eat: food => logFn(`Eat ${food}.`),
    sleep: ms => sleep(ms * 1000).then(() => logFn(`Wake up after ${ms} second${ms > 1 ? 's' : ''}.`)),
  }

  Promise.resolve().then(exec)

  async function exec() {
    for (const [cmd, val] of cmds) {
      await actions[cmd](val)
    }
  }

  return {
    sleep(ms) {
      cmds.push(['sleep', ms])
      return this
    },
    sleepFirst(ms) {
      cmds.unshift(['sleep', ms])
      return this
    },
    eat(food) {
      cmds.push(['eat', food])
      return this
    },
  }
}

来自 JavaScript Coding Questions / 130. create LazyMan() / Discuss

@fedono
Copy link
Owner Author

fedono commented Sep 25, 2023

  const timeout = (time) => new Promise(resolve => {
    setTimeout(resolve, time * 1000)
  })
  
  const scheduler = new Scheduler()
  const addTask = (time, order) => {
    scheduler.add(() => timeout(time))
      .then(() => console.log(order))
  }

//   每次最多执行两个
  addTask(1, 'jack1')
  addTask(2, 'jack2')
  addTask(3, 'jack3')
  addTask(4, 'jack4')

解法和 asyncQueue 差不多

class Scheduler {
    tasks = []
    count = 0
    add(promiseCreator) { 
        return new Promise((resolve, reject) => {
            // 这一步非常之关键,需要将 resolve/reject 都添加进去,这样每个 add 后面的 then,其实都可以获取当前运行的结果
            this.tasks.push([promiseCreator, resolve, reject])

            this.exec()
        })
     }

     async exec() {

        if (this.tasks.length === 0) {
            return
        }

        if (this.count >= 2) {
            return;
        }

        this.count++
        const [fn, resolve, reject] = this.tasks.shift()

        try {
            const res = await fn()
            resolve(res)
        } catch (error) {
            reject(error)            
        } finally {
            this.count--;

            this.exec()
        }
     }
  }
  

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant