Open fedono opened 4 years ago
贴个自己完成的
重点有三个
- 首先就是方法放在 prototype 上,要不然拿不到对应的方法
- 第二是需要返回 this,要不然无法向下衔接
- 第三则是每次运行完当前的方法,则直接执行下一个方法
- 第四则是需要使用 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;
}
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
贴一个实现的比较好的版本,不用 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
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()
}
}
}
问题
实现一个LazyMan,可以按照以下方式调用:
解法
对于这个问题,首先创建一个任务队列,然后利用next()函数来控制任务的顺序执行:
参考