js链式调用
reference:zhuanlan.zhihu.com/p/136230955
实现一个懒汉,并且提供一系列的行为方法,调用 eat 就打印吃饭信息,调用 sleep 方法则进行延迟传入时间,再进行下一步的操作。
限制不使用promise
实现一个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
以此类推。
分析
链式调用
从LazyMan("hank").sleep(5000).eat("supper")可以看出每一个方法解围都可以通过 . 来 注册或者执行下一步,所以想到返回this
sleep延迟
如何使用sleep方法延迟后续方法执行,可以使用settimeout,那么setTimeout如何能通知在一定时间内再把方法推进事件队列,但我们无法阻碍下一步方法的调用.能这么想,说明我们把链式调用想象为马上执行,就好像是下面代码
// 错误的代码
sleep (timeout) {
setTimeout(() => {
console.log(`${this.name} end sleep ~`)
}, timeout * 1000)
return this
}
这是错误的,我们无法等待一个异步执行完后才去执行下一个同步,执行sleep函数后会立刻返回this之后进行下一个函数.
那么如果我们维护一个队列,就像是promise那样,函数调用只是注册,之后到点了才执行.
维护队列和调度顺序
我们把链式调用定义为一个注册行为,事情就好办了,使用观察者模式(??,一个坑,是和发布订阅模式相似的那个么),首先lazyman在链式调用的每一个环节都会往队列中注册一个方法,在队列调度期间,程序从最前面的方法执行,队列中每一个方法运行结束都会调用下一个方法执行,事情变得井然有序(想到了promise并发执行但是有limit的情况),我们何时开始队列的第一个函数执行?
调度时机
开始触发队列中第一个方法的时机是什么?当然可以使用一个额外的start方法触发队列的调度,但是这题不许,那么怎么办呢,事实上,由于队列循环机制,可以使用setTimeout来解决,在构造函数开始一步调度.(这个选择非常妙,我也见过类似用法,在宏任务执行完成后,微任务才开始执行,这里就是等到注册全部完成,再开始任务,如果在原构造函数直接执行,就会导致执行第一个构造函数的时候,没有事件,所以执行了个空气,但是后面才注册,注册了又没法开始.
所以是先把代码都放到js引擎,执行,之后发现没有微任务,所以跑回来执行宏任务settimeout
class LazyMan {
constructor (name) {
// any code ...
setTimeout(() => {
this.next() // 负责调度下一个方法执行
},0)
}
}
(原文这里还把宏任务,微任务都解释一遍,也是不错)
// 懒汉 类
class LazyManClass {
constructor (name) {
this.name = name
this.queue = [] // 队列
console.log(`Hi! This is ${name}`)
// 延迟调度
setTimeout(() => {
this.next()
},0)
}
// 调度方法
next () {
const fnc = this.queue.shift()
fnc && fnc()
}
/**
* 注册函数方法
* @param {*} fn 要注册的函数
* @param {*} isFirst 是否注册在队列最前
*/
register (fn, isFirst) {
if (isFirst) {
this.queue.unshift(fn)
} else {
this.queue.push(fn)
}
}
// 吃
eat (food) {
const _eat = () => {
console.log(`Eat ${food}~`)
this.next()
}
this.register(_eat)
return this
}
// 睡在最前面
sleepFirst (s) {
return this.sleep(s, true)
}
// 睡觉
sleep (s, isFirst=false) {
const timeout = s * 1000
const _sleep = () => {
console.log(`Wake up after ${s}`)
setTimeout(() => {
this.next()
}, timeout)
}
this.register(_sleep, isFirst)
return this
}
}
// 懒汉 返回一个懒汉实例
function LazyMan (name) {
return new LazyManClass(name)
}
interpret
- 首先是链式调用,提议在方法结尾return this,
- 使用队列,通过register注册方法进行函数注册,在链式调用过程中按照顺序推到队列中
- 利用事件循环,在构造函数中通过setTimeout将调度队列的时机放到之后的事件循环中进行.
后记 2022.3.19
在看Symbol.iterator时,看到了一个异步的迭代器Symbol.asyncIterator,又想到了这个题目,感觉可以通过这个实现一下
首先介绍一下symbol.iterator
symbol.iterator
在调用for (let i of iterable)时,首先是iterable调用 iterable[Symbol.iterator]() 函数,返回一个对象,这个对象里有一个方法next(),之后通过不断调用next()函数,就可以获得一个返回值,返回值一定要包含两个属性,value,done. done用true或者false表示遍历是否结束.具体图例如下
for(let i of iterable ) { -> iterable[Symbol.iterator]()=>获得一个对象
iterator={next:function(){}}
console.log(i) -> let iter=iterator.next(); //{value:"value",done:false}
-> if(!iter.done){
i=iter.value;
//let 形成多个块级作用域
iter=iterator.next();
} =>进行下一个值
-> else {break;} 退出循环
}
symbol.asyncIterator
原理和上面的类似只不过遍历对象要用promise来包装,并且返回的值放在resolve函数中传递给then最后赋给遍历获得的参数.遍历时使用
for await(let i of asyncIterable){}
并且注意如果写在函数中,函数前面要加async(await标配)
一个简单的例子如下
<script>
const asyncIterable = {
[Symbol.asyncIterator]() {
let i=0;
return {
next() {
if (i < 3) {
return new Promise((resolve)=>{
setTimeout(()=>{
resolve({value:i,done:false});
i++;
},2000);
})
}
return Promise.resolve({ done: true });
}
};
}
};
(async function() {
for await (let num of asyncIterable) {
console.log(num);
}
})();
</script>
结果间隔2s输出0,1,2; i在这里形成了闭包使用
LazyMan
想了一下,其实使用异步迭代器只不过是改变了上面this.next(),改成了asyncIterator,大体思路还是一致.记录要使用的函数,返回this,使用setTimeout触发.改成自动next()罢了
首先建立一个类,类中有一个queue,而这个queue有一个异步迭代器属性由我们自己来写,大体框架如下
class LazyMan {
constructor(name) {
console.log("this is ", name);
this.queue = {
que: [],
[Symbol.asyncIterator]: () => {
let i = 0;
let self = this;
return {
//这里不用箭头函数咋搞this啊
next: () => {
return
});
},
};
},
};
let trig = this.trigger.bind(this);
setTimeout(trig, 0);
}
sleep(time) {
this.queue.que.push(() => {
setTimeout(() => {
console.log("sleep ", time);
}, time);
});
return this;
}
eat(food) {
this.queue.que.push(() => {
//console.log("eat " + food);
});
return this;
}
async trigger() {
for await (let num of this.queue) {
console.log(num);
}
}
}
let person = new LazyMan("HH")
.eat("cola")
.sleep(2000)
.eat("bobA")
.sleep(3000)
.eat("garbage");
在trigger函数里遍历,就可以获得自动执行next()的好处(好处好小...,写完就发现多写了好多行),这里没有直接使用this.queue作为数组是因为一开始出了个bug,改成这样,后来忘改回去了,应该问题不大
首先是在何处new promise.promise一旦建立就开始执行exector函数里的内容,如果在注册的时候new promise,就不能做成一个一个依次执行的效果,所以要在执行next()函数的时候new promise
那么,压到queue里面的就是一个个函数,这个函数包装到next()构造的promise中,等到这个函数执行完后才能resolve,并且resolve函数的参数应该是一个 {value:函数执行完的返回值,done: 是否到达queue的终点} . done:true时,value应为undefined
[Symbol.asyncIterator] = () => {
let i = 0;
let self = this;
return {
next: () => {
return new Promise((resolve, reject) => {
if (typeof self.queue.que[i] === "function") {
self.queue.que[i]();
//resolve()
i++;
});
} else {
reject({
value:
typeof self.queue.que[i] === "function" && self.queue.que[i](),
done: true,
});
}
});
},
};
};
首先要执行queue里的函数,并获得函数执行结果作为返回值.并且要在执行完,
resolve({value:`return of function`,done);
想到对于一般的sleep函数,一般是
new Promise((resolve,reject)=>{
setTimeout(()=>{
//do something
resolve(res);
},time)
})
到这里有两个问题
- resolve要在setTimeout的回调函数里执行,但是我们这个回调函数已经固定了,就是类中的eat和sleep,那么如何做到这个呢.
- resolve 执行时,value的值需要queue里面的函数执行的返回值,而这个要先执行这个函数才能resolve,这就形成了矛盾
第一个问题,改变了一下调用的方式,在加入queue时的函数的参数增加一个回调函数,表示在执行完成正常操作后才执行,这样就可以在setTimeout里面执行resolve了
this.queue.que.push((callback) => {
setTimeout(() => {
console.log("sleep ", time);
if (typeof callback !== "function")
throw new Error("callback not function,not you mistake");
callabck();
}, time);
return "sleep " + time + " return";
});
//a example for callback
(resolve,reject)=>{
this.queue.que[i](()=>{
resolve(data);
i++
})
}
这样就可以在执行完后在setTimeout中使用resolve让promise变成fulfilled
第二个问题,考虑到js中对象是引用值,考虑如下
let res={value:null,done:i>=self.queue.que.length-1};
res.value = self.queue.que[i](() => {
resolve(res);
i++;
});
这样一边执行函数,还能返回函数的结果
最终代码
class LazyMan {
constructor(name) {
console.log("this is ", name);
this.queue = {
que: [],
record: [],
//这里不用箭头函数咋搞this啊
[Symbol.asyncIterator]: () => {
let i = 0;
let self = this;
return {
next: () => {
return new Promise((resolve, reject) => {
if (typeof self.queue.que[i] === "function") {
let res = {
value: null,
done: i >= self.queue.que.length - 1,
};
res.value = self.queue.que[i](() => {
// console.log("resolve ",i);
resolve(res);
self.queue.record[i] = "1";
i++;
});
} else {
reject({
value:
typeof self.queue.que[i] === "function" &&
self.queue.que[i](),
done: true,
});
}
});
},
};
},
};
let trig = this.trigger.bind(this);
//bind this or the function inside won`t work
//or apply triggger in arrow function way
// setTimeout(()=>this.trigger(),0);
setTimeout(trig, 0);
}
sleep(time) {
this.queue.que.push((callback) => {
setTimeout(() => {
console.log("sleep ", time);
if (typeof callback !== "function")
throw new Error("callback not function,not you mistake");
callback();
}, time);
return "sleep " + time + " return";
});
return this;
}
eat(food) {
this.queue.que.push((callback) => {
console.log("eat " + food);
// typeof callback === "function" && callback();
if (typeof callback !== "function")
throw new Error("callback not function,not you mistake");
callback();
return "eat " + food + " return";
});
return this;
}
async trigger() {
// console.log(this);
for await (let num of this.queue) {
console.log(num);
}
}
}
let person = new LazyMan("HH")
.eat("cola")
.sleep(2000)
.eat("bobA")
.sleep(3000)
.eat("garbage");
最终结果
diy.html:31 this is HH
diy.html:88 eat cola
diy.html:100 eat cola return
diy.html:76 sleep 2000
diy.html:100 sleep 2000 return
diy.html:88 eat bobA
diy.html:100 eat bobA return
diy.html:76 sleep 3000
diy.html:100 sleep 3000 return
diy.html:88 eat garbage
ps
有一个小问题,最后一个没有return结果
若改成
done: i > self.queue.que.length - 1,
则报错
已修改
上述错误是因为最后有效值的时候 done也应为false,理解错误.
以下摘自es6.ruanyifeng.com/#docs/itera…
var it = makeIterator(['a', 'b']);
it.next() // { value: "a", done: false }
it.next() // { value: "b", done: false }
it.next() // { value: undefined, done: true }
function makeIterator(array) {
var nextIndex = 0;
return {
next: function() {
return nextIndex < array.length ?
{value: array[nextIndex++], done: false} :
{value: undefined, done: true};
}
};
}
修改后next如下
next: () => {
return new Promise((resolve, reject) => {
if(i>=self.queue.que.length){
resolve({value:undefined,done:true});
}
if (typeof self.queue.que[i] === "function" ) {
let res = {
value: null,
done: i >= self.queue.que.length ,
};
res.value = self.queue.que[i](() => {
// console.log("resolve ",i);
resolve(res);
self.queue.record[i] = "1";
i++;
});
} else {
reject({
value:
typeof self.queue.que[i] === "function" &&
self.queue.que[i](),
done: true,
});
}
});
},
虽然可能不太正经,还忘记看题目不许使用promise,就权当练习玩了.