Roll your own Promise(2)

[Musings]
在连江的第二天,感觉不错,一天没喝酒很舒服,lol
靠海吃海,全是新鲜的海鲜,继续写Promise,应该每天先花时间写算法和设计模式的,然后再去开启一天的生活,不然很多时间都被其他东西占有了,不合理。重要的事得先行

Now, let’s come to the coding part, share the code snippet first

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
type State = "pending" | "fulfilled" | "rejected";
class PromiseI {
private state: State;
private fnList: Function[];
private val: any;
constructor(executer: (acc, rej) => void) {
this.state = "pending";
this.fnList = [];
executer(this.resolve.bind(this), this.reject.bind(this))
}

resolve(val: string) {
if (this.state == "pending") {
this.val = val;
this.state = "fulfilled";
console.log("this is resolve:", val);
if (this.fnList.length > 0) {
for (const fn of this.fnList) {
queueMicrotask(() => fn());
}
}
} else {
console.error("can't update the state");
}
}

reject(val: string) {
if (this.state == "pending") {
this.state = "rejected";
this.val = val;
console.log("this is reject:", val);
if (this.fnList.length > 0) {
for (const fn of this.fnList) {
queueMicrotask(() => fn());
}
}
} else {
console.error("can't update the state");
}
}

then(onFulfilled: Function, onRejected?: Function) {
return new PromiseI((resolve, reject) => {
const run = () => {
queueMicrotask(() => {
if (this.state == 'fulfilled') {
try {
const x = onFulfilled(this.val);
resolve(x);
}
catch (e) {
reject(e);
}
}
else if (this.state == 'rejected' && onRejected) {
try {
const x = onRejected(this.val);
resolve(x);
} catch (e) {
reject(e)
}
}
})
}
if (this.state == "pending") {
this.fnList.push(run);
} else {
run();
}
});
}
}

//test case
let promise = new PromiseI((resolve, reject) => {
resolve("success");
});
promise.then((res) => {
console.log("then", res);
return "yes"
});
promise.then((res) => {
console.log("then", res);
return "lala"
});

Here are some points I want to hightlight:

  1. The constructor part should clear with the bind
  2. Resolve and Reject function should pluge the function list and use queueMicrotask to handle the logic
  3. Then function need to use method chaining, and the value should be transfer to the next promise.

It’s a little complicated to implemente for Promise, but keep ongoing… I trust I am doing the right things.

Roll your own Promise

[Musings]
今天在连江县,所谓的一县两省,台湾和福建省,都有居民,在这吃了很多海鲜,还喝酒了,中午和晚上都喝了点,实在是酒量不行,困得要死,影响了今天的进度。先写一版吧,迭代估计得明天再来了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
type State = "pending" | "fulfilled" | "rejected";
class PromiseI {
private state: State;
private fnList: Map<string, Function>;
constructor(executer: (acc, rej) => void) {
executer(this.resolve.bind(this), this.reject.bind(this))
}

resolve(val: string) {
this.state = "fulfilled";
console.log("this is resolve:",val);
}

reject(val: string) {
this.state = "rejected";
console.log("this is reject:",val);
}

then(fn:Function) {
fn();
}
}
let p = new PromiseI(
(resolve,reject)=>{
resolve("test");
}
)

p.then(()=>{
console.log("here is callback then")
})

Roll your own Pub/Sub

[Musings]
国庆第一天,参加完朋友的宝宝宴,晚上出发去潮州吃点东西,现在在pd机场写post,今天也得完成练手任务。Gogogo

今天手写Pub/Sub,其实就是解耦了昨天的EventEmitter,EventEmitter天然的热流,不带回放功能,一个列表维护on/emit,而pub/sub解耦以后可以玩的空间就更大了,能做replay回放/粘性,更可以去创建冷流

Let’s roll the first version

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
class Subscriber {
private bus: Bus;
constructor(bus: Bus) {
this.bus = bus;
}
subscribe(event: string, fn: Function) {
this.bus.subscribe(event, fn);
}
}

class Publisher {
private bus: Bus;
constructor(bus: Bus) {
this.bus = bus;
}
publish(event: string, info: string) {
this.bus.publish(event, info, info)
}
}

class Bus {
private channels: Map<string, Function[]>
constructor() {
this.channels = new Map();
}
publish(event: string, ...args: any[]) {
const channel = this.channels.get(event);
if (!channel) this.channels.set(event, []);
else {
channel.forEach(
fn => fn.apply(this, args)
);
}
}
subscribe(event: string, fn: Function) {
const channel = this.channels.get(event);
if (!channel) this.channels.set(event, [fn]);
else {
channel.push(fn);
}
}
}

let bus = new Bus();
let pub1 = new Publisher(bus);
let subscribe1 = new Subscriber(bus);
let pub2 = new Publisher(bus);
let subscribe2 = new Subscriber(bus);
let subscribe3 = new Subscriber(bus);
pub1.publish("hit", "hithit")
pub2.publish("touch", "touchtouch")

subscribe1.subscribe("hit", (...info) => console.log('this is subscribe1 got info: ', info));
subscribe2.subscribe('touch', (...info) => console.log('this is subscribe2 got info: ', info));

subscribe3.subscribe("hit", (...info) => console.log('this is subscribe3 got info: ', info));
subscribe3.subscribe('touch', (...info) => console.log('this is subscribe3 got info: ', info));

pub1.publish("hit", "hithit2")
pub2.publish("touch", "touchtouch2")

Output:

Now, we need to implement for the replay feature. So we need to creat a store for memorization.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
class SubscriberV2 {
private bus: BusV2;
constructor(bus: BusV2) {
this.bus = bus;
}
subscribe(event: string, fn: Function) {
this.bus.subscribe(event, fn);
}
}

class PublisherV2 {
private bus: BusV2;
constructor(bus: BusV2) {
this.bus = bus;
}
publish(event: string, info: string) {
this.bus.publish(event, info)
}
}

class BusV2 {
private channels: Map<string, Function[]>
private stores: Map<string, string[]> // create a new store to store the value for Behavior/Replay
constructor() {
this.channels = new Map();
this.stores = new Map();
}
publish(event: string, ...args: any[]) {
const channel = this.channels.get(event);
if (!channel) this.channels.set(event, []);
else {
channel.forEach(
fn => fn.apply(this, args)
);
}
const store = this.stores.get(event);// Check whethere we need already created
if (!store) this.stores.set(event, [...args]);
else {
store.push(...args);
}
}
subscribe(event: string, fn: Function) {
const channel = this.channels.get(event);
if (!channel) this.channels.set(event, [fn]);
else {
channel.push(fn);
}
const store = this.stores.get(event);// check whether need to replay
if (store) {
store.forEach(
(info) => {
fn(info);
}
)
}
}
}

console.log("*".repeat(40));
let busv2 = new BusV2();
let pubV2 = new PublisherV2(busv2);
pubV2.publish("click", 'hi 1');
pubV2.publish("click", 'hi 2');
let subV2 = new SubscriberV2(busv2);
subV2.subscribe("click",(info)=>{console.log("received click info and say: ", info)});

Output:

Roll your own EventEmitter

[Musings]
今天又是下雨的一天,写完这个 post 就去花园里把自动浇水系统搭了。拖了一周没搞了。😓

今天是手写 EventEmitter 的一天,边写边改,一个版本一个版本的迭代,主要就是熟练下一些基础的结构,从头写一遍,下面就开始正片了

第一个版本主要就是实现 on 和 emit 函数

here comes the first version of on() and emit() function

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
console.log("*".repeat(40), "version 1");
class EventEmitter {
private eventRegister: Map<string, Function>;
constructor() {
this.eventRegister = new Map();
}
on(event: string, fn: Function) {
this.eventRegister.set(event, fn);
}
emit(event: string, info: string) {
this.eventRegister.get(event)!(info);
}
}

let bus = new EventEmitter();
bus.on("click", (val) => console.log("you clicked!✅ and say", val));
bus.emit("click", "Yaaa");

bus.on("touch", (val) => console.log("you touch!✅ and say", val));
bus.emit("touch", "Humm");
bus.emit("click", "Yaaa");
bus.emit("touch", "Humm");

first step is to think about the data structure. The most straightforward choice is to use Map, because we need to use a <K,V> for storage

here terminal will output with:

now, the first issue comes, when we use Map<event,Function> as the data structure then we will face how to trigger the same event with different callback function… problems like below.

1
2
3
bus.on("click", () => console.log("click1"));
bus.on("click", () => console.log("click2"));
bus.emit("click", "hi");

the second log will overlap the first one,because if there is existed key in Map, then set() will update with the value and overlap the previous valu.
so Map<K,V>should use -> Map<string, Function[]> instead of Map<string, Function>

then we will naturally think of below version 2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
console.log("*".repeat(40), "version 2");
class EventEmitterV2 {
private eventRegister: Map<string, Function[]>;
constructor() {
this.eventRegister = new Map();
}
on(event: string, fn: Function) {
const fnList = this.eventRegister.get(event);
if (fnList) fnList.push(fn);
else this.eventRegister.set(event, [fn]);
}
emit(event: string, info: string) {
this.eventRegister.get(event)?.forEach((fn) => fn(info));
}
}

let busV2 = new EventEmitterV2();
busV2.on("click", (val) => console.log("you clicked!✅ and say", val));
busV2.emit("click", "Yaaa");

busV2.on("click", (val) => console.log("you strong click!🚔 and say", val));
busV2.emit("click", "Yaaa");

Now it works as expected 🎉.

Next, let’s consider the off feature.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
console.log("*".repeat(40), "version 3");
class EventEmitterV3 {
private eventRegister: Map<string, Function[]>;
constructor() {
this.eventRegister = new Map();
}
on(event: string, fn: Function) {
const fnList = this.eventRegister.get(event);
if (fnList) fnList.push(fn);
else this.eventRegister.set(event, [fn]);
}
emit(event: string, info: string) {
this.eventRegister.get(event)?.forEach((fn) => fn(info));
}

off(event: string, fn: Function) {
if (!this.eventRegister.get(event)) return;
const fnList = this.eventRegister.get(event)?.filter((f) => f !== fn);
this.eventRegister.set(event, fnList!);
}
}

it’s not a big deal right? The off() method simply finds the event and use use filter to rebuild the listener list.

Now let’s consider the once() method. It’s a little trickier.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
console.log("*".repeat(40), "version 3");
class EventEmitterV4 {
private eventRegister: Map<string, { fn: Function, onceFlag: boolean }[]>;//we need to update the structure
constructor() {
this.eventRegister = new Map();
}

on(event: string, fn: Function, onceFlag: boolean = false) {
const fnList = this.eventRegister.get(event);
if (fnList) fnList.push({ fn, onceFlag }); // need to baesed on the structure
else this.eventRegister.set(event, [{ fn, onceFlag }]);// need to baesed on the structure
}

once(event: string, fn: Function, onceFlag: boolean = false) {
const fnList = this.eventRegister.get(event);
if (fnList) fnList.push({ fn, onceFlag });// need to baesed on the structure
else this.eventRegister.set(event, [{ fn, onceFlag }]);// need to baesed on the structure
}

emit(event: string, info: string) {... }

off(event: string, fn: Function) {... }

}

This approach is intuitive and easy to understand, but it doesn’t feel elegant. As developers, we value elegance, so let’s use a neat trick to implement it. Here’s where the magic comes in:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
console.log("*".repeat(40), "version 4");

class EventEmitterV4 {
private eventRegister: Map<string, Function[]>
constructor() {
this.eventRegister = new Map();
}
on(event: string, fn: Function) {
const fnList = this.eventRegister.get(event);
if (fnList) fnList.push(fn);
else this.eventRegister.set(event, [fn]);
}
emit(event: string, info: string) {
this.eventRegister.get(event)?.forEach(fn => fn(info))
}

off(event: string, fn: Function) {
if (!this.eventRegister.get(event)) return;
const fnList = this.eventRegister.get(event)?.filter(f => f !== fn);
this.eventRegister.set(event, fnList!);
}

once(event: string, fn: Function) {
//nothing changed above, just to use a wrapper to implement this feature.
const wrapper = (args) => {
this.off(event, wrapper);
fn(args);
}
this.on(event, wrapper);%t

// For newcomers to TypeScript, this might feel a bit hard to understand, but think of it this way:
1. this. on(event,fn()); // this is what we want, but we need to invoke the off() after fn()
// then we create a wrapper to do it, and set the wrapper to this.on(..)
}
}

let fnV4 = (val) => console.log("only once trigger", val);
const eventEmitterV4 = new EventEmitterV4();
eventEmitterV4.once("click", fnV4);
eventEmitterV4.emit("click", "onClick");
eventEmitterV4.emit("click", "onClick");
eventEmitterV4.emit("click", "onClick");

Finally, here’s the complete log output:

BB repo url: https://github.com/XProfessorJ/code-evolution-90days/blob/main/src/02-EventEmmitter.ts

Roll your own debounce / throttle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function debounce(fn: Function, interval: number) {
let timer: any = null;
return function () {
//之所以这里return函数,是为了让timer的作用于扩展,能保证多个function受同一个timer影响
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => fn(), interval);
};
}

//这里的逻辑是,拿到了debouce方法返回的函数引用,然后调用这个函数引用3次,那只有第一次进去的时候才会开始timer
const log = debounce(() => console.log(1), 2000);
log();
log();

//继续理解下面的逻辑,为什么log2和log1打印的时候不会共用一个timer,原因是因为下面调用debounce的时候返回的不是同一个函数引用,所以是隔离的
const log2 = debounce(() => console.log(2), 2000);
log2();
log2();
log2();

//output:
// 1
// 2

同理,继续思考throttle的写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
function throttle(fn: Function, interval: number): Function {
let timer: any = null;
return function () {
if (!timer) timer = setTimeout(() => {
fn();
timer = null;//这里要注意把timer最后设置为null,不然只有第一次函数会执行,再往后就不会执行了,可以注释掉这行,看第一个log的运行逻辑
}, interval)
return;
}
}

//这个log可以看到没有timer设置为null的样子
const log = throttle(() => console.log('hit'), 1000);
log(); // 1s 后打印 'hit'
setTimeout(log, 1500);


const fn = () => { console.log(1) }
const log2 = throttle(fn, 2000);
const log3 = throttle(() => console.log(2), 3000);
log2();
log2();
log3();
log3();

//output:
// hit
// 1
// hit
// 2

在debounce和throttle的case里,去理解闭包在拉长变量作用域的作用,timer锁在返回函数的作用域里,延长它的生命周期,如果没有闭包的情况下,timer会在debounce和throttle函数在被调用以后立即被销毁,在下一次调用的时候就没法拿到上一次的timer定时器的引用了
有了闭包,就等于可以记忆timer了,从而实现只执行最后一次固定时间只执行一次的效果

When we typing in Chrome

Outline

  1. DNS
  2. TCP handshake
  3. TLS connection
  4. HTTP / HTTPS
  5. TCP

DNS

When we typing domain name in chrome, then …

  1. Chrome first checks its own DNS cache.
  2. If not found, the system checks the operating system’s DNS cache and the hosts file.
  3. If still not found, the request is sent to the configured recursive resolver
  4. The recursive resolver performs the lookup step by step:
    • It asks a root server, which points to the relevant top-level domain (TLD) server (e.g., .com).
    • Then it queries the TLD server, which points to the authoritative server for the domain (e.g., example.com).
    • Finally, it queries the authoritative DNS server, which provides the actual IP address.
  5. The IP address is returned to the client, and Chrome uses it to establish the connection.

TCP handshake

  1. Client side will send api with [SYNC = 1], Seq = i,
  2. Server side will return api with [SYNC = 1, ACK = 1] , Ack = i+1, Seq = u
  3. Client side will send api with [ACK = 1], Ack = u+1

TCP handshake(by wireshark)

TLS Connection

HTTP / HTTPS

TCP disconnection

  1. Client send [FIN = 1, ACK = 1], seq = u,
  2. Server side send [ ACK = 1], Seq = j, Ack = u+1
  3. Server side send [FIN = 1, ACK = 1], Seq = j, Ack = u+1
  4. Client send [ACK = 1], Seq = x, Ack = j+1
    TCP handshake(by wireshark)