- Published on
BigFrontEnd Category 12 node Implementation Questions
- Authors
- Name
- Yinhuan Yuan
Introduction
This blog post summarizes the node implementation related questions found on BigFrontEnd.Dev.
1.create an Event Emitter
16.https://bigfrontend.dev/problem/create-an-Event-Emitter There is Event Emitter in Node.js, Facebook once had its own implementation but now it is archived.
You are asked to create an Event Emitter Class
const emitter = new Emitter()
It should support event subscribing
const sub1 = emitter.subscribe('event1', callback1)
const sub2 = emitter.subscribe('event2', callback2)
// same callback could subscribe
// on same event multiple times
const sub3 = emitter.subscribe('event1', callback1)
emit(eventName, ...args) is used to trigger the callbacks, with args relayed
emitter.emit('event1', 1, 2)
// callback1 will be called twice
Subscription returned by subscribe() has a release() method that could be used to unsubscribe
sub1.release()
sub3.release()
// now even if we emit 'event1' again,
// callback1 is not called anymore
Solution:
// Add this to access the class property
// callbacks.splice(index, 1); delete the number of elements after index.
class EventEmitter {
#eventHandlers = new Map()
subscribe(eventName, callback) {
if (this.#eventHandlers.has(eventName)) {
this.#eventHandlers.get(eventName).push(callback)
} else {
this.#eventHandlers.set(eventName, [callback])
}
return {
release: () => {
const callbacks = this.#eventHandlers.get(eventName)
const index = callbacks.indexOf(callback)
if (index === -1) {
return
}
callbacks.splice(index, 1)
},
}
}
emit(eventName, ...args) {
if (this.#eventHandlers.has(eventName)) {
this.#eventHandlers.get(eventName).forEach((callback) => {
callback(...args)
})
}
}
}
const emitter = new Emitter()
const sub1 = emitter.subscribe('event1', callback1)
const sub2 = emitter.subscribe('event2', callback2)
// same callback could subscribe
// on same event multiple times
const sub3 = emitter.subscribe('event1', callback1)
emitter.emit('event1', 1, 2)
// callback1 will be called twice
sub1.release()
sub3.release()
// now even if we emit 'event1' again,
// callback1 is not called anymore
2.create a simple store for DOM element
17.https://bigfrontend.dev/problem/create-a-simple-store-for-DOM-node We have Map in es6, so we could use any data as key, such as DOM element.
const map = new Map()
map.set(domNode, somedata)
What if we need to support old JavaScript env like es5, how would you create your own Node Store as above?
You are asked to implement a Node Store, which supports DOM element as key.
class NodeStore {
set(node, value) {}
get(node) {}
has(node) {}
}
note
Map is disabled when judging your code, it is against the goal of practicing.
You can create a simple general Map polyfill. Or since you are asked to support specially for DOM element, what is special about DOM element?
What is the Time / Space cost of your solution?
Solution:
class NodeStore {
constructor() {
this.nodes = {}
}
/**
* @param {Node} node
* @param {any} value
*/
set(node, value) {
node.__id__ = Symbol()
this.nodes[node.__id__] = value
}
/**
* @param {Node} node
* @return {any}
*/
get(node) {
return this.nodes[node.__id__]
}
/**
* @param {Node} node
* @return {Boolean}
*/
has(node) {
// coerce to boolean value
return !!this.nodes[node.__id__]
}
}
3.create a middleware system
52.https://bigfrontend.dev/problem/create-a-middleware-system
Have you ever used Express Middleware before?
Middleware functions are functions with fixed interface that could be chained up like following two functions.
app.use(
'/user/:id',
function (req, res, next) {
next()
},
function (req, res, next) {
next(new Error('sth wrong'))
}
)
You are asked to create simplified Middleware system:
type Request = object
type NextFunc = (error?: any) => void
type MiddlewareFunc =
(req: Request, next: NextFunc) => void
type ErrorHandler =
(error: Error, req: Request, next: NextFunc) => void
class Middleware {
use(func: MiddlewareFunc | ErrorHandler) {
// do any async operations
// call next() to trigger next function
}
start(req: Request) {
// trigger all functions with a req object
}
}
Now we can do something similar with Express
const middleware = new Middleware()
middleware.use((req, next) => {
req.a = 1
next()
})
middleware.use((req, next) => {
req.b = 2
next()
})
middleware.use((req, next) => {
console.log(req)
})
middleware.start({})
// {a: 1, b: 2}
Notice that use() could also accept an ErrorHandler which has 3 arguments. The error handler is triggered if next() is called with an extra argument or uncaught error happens, like following.
const middleware = new Middleware()
// throw an error at first function
middleware.use((req, next) => {
req.a = 1
throw new Error('sth wrong')
// or `next(new Error('sth wrong'))`
})
// since error occurs, this is skipped
middleware.use((req, next) => {
req.b = 2
})
// since error occurs, this is skipped
middleware.use((req, next) => {
console.log(req)
})
// since error occurs, this is called
middleware.use((error, req, next) => {
console.log(error)
console.log(req)
})
middleware.start({})
// Error: sth wrong
// {a: 1}
Solution:
class Middleware {
constructor() {
this.middlewareFuncs = []
this.errorHandlerFuncs = []
}
use(func) {
// func.length returns the number of function parameters.
if (func.length < 3) {
// (req, next)
this.middlewareFuncs.push(func)
} else {
// (error, req, next)
this.errorHandlerFuncs.push(func)
}
}
start(req) {
let middlewareIndex = 0
let errorHandlerIndex = 0
const next = (error) => {
if (error) {
// If an error is passed, find the next error handler
if (errorHandlerIndex >= this.errorHandlerFuncs.length) {
return
}
const fn = this.errorHandlerFuncs[errorHandlerIndex]
errorHandlerIndex += 1
try {
fn(error, req, next)
} catch (e) {
next(e)
}
} else {
// Otherwise, execute the next middleware function
if (middlewareIndex >= this.middlewareFuncs.length) {
return
}
const fn = this.middlewareFuncs[middlewareIndex]
middlewareIndex += 1
try {
fn(req, next)
} catch (e) {
next(e)
}
}
}
next()
}
}