An annuity three silver four come, it seems that “for handwritten interview/machine code” has become one of the necessary to interview some company, also occupies the important position in the Internet an interview, when be handwritten interview code, first self-confidence, there is a pen is to, is a piece of writing, a painting, and then finish delete… If it is in the whiteboard, it should be repeated painting, because there is no thinking, no good ideas, the final result is full of loopholes, quite not smooth, after joking: “what time? One library and write code by hand?” .

Why write code by hand?

The community already has some libraries, so why write them by hand? Of course, this is not necessary, talking about my own thinking, in the actual development process, I will choose some excellent libraries first, and will not build wheels everywhere, otherwise it will increase the cost of maintenance.

Why do people sometimes talk about “CURD engineers/API engineers”? If I can only call API, even if its implementation is very simple, I don’t know. If I can think more in my work and learn and think more about something I often use, on the one hand, I can deepen my understanding. For example, what will happen if the resolve function of Promise is not implemented? An implementation of concurrent request control, “Maximizing the number of concurrent requests in the browser,” was written earlier to take advantage of this Promise. On the other hand, if we understand the implementation behind it, we can also reflect on whether there is room for optimization. Aren’t excellent projects constantly summarized and iteratively optimized?

Variable name definitions, function or interface design, code readability, and details can also reveal a candidate’s coding skills and habits when writing code. Make it a good habit to do your regular job, not just interview for interview’s sake.

Whether you write code by hand or by machine, the so-called “hand-torn code” is not the ultimate goal, as an interviewer should not only take the final results as the final evaluation, can pay more attention to the implementation of some ideas.

When I see some libraries or articles, I will record the points I am interested in and try to write them down. Only then can I see “how is certain API implemented?” I remember one of my friends asked me a question about the implementation of Node.js Stream pipe. I happened to be interested in the implementation of node.js Stream pipe. After reading the relevant source code of Node.js, I got this article about the use and implementation principle analysis of Nodejs Stream pipe.

Here are 10 JavaScript related questions that I hope will help you. Good Luck!

Ten questions

  • The array dimension reduction
  • Array/object array decrement
  • Deep copy
  • Implement a sleep function
  • Currization function implementation
  • Implement a new/instanceof operator
  • Call /apply/bind three brothers
  • Realize the map/reduce
  • Handwritten Promise implementation of each method
  • Implementation principle of Co/Async

The array dimension reduction

Implementation idea:

  • Define the arrReduction method to accept an array parameter
  • Line {1} calls the recursive function arrReductionRecursive(arr, []). The second argument is optional. You can also set the default value for line {2}, which requires ES6 support
  • Line {3} uses forEach to loop through the array
  • Line {4} detects that the currently traversed element continues the recursive traversal of the array
  • Line {5} holds the result if the current element is not an array
  • Line {6} returns the result
/** * array dimension reduction *@param { Array } arr 
 * @returns { Array } Returns a completed array */
function arrReduction(arr) {
  return arrReductionRecursive(arr, []); / / {1}
}

/** * array dimension reduction recursive call *@param { Array } arr 
 */
function arrReductionRecursive(arr, result=[]) { / / {2}
  arr.forEach(item= > { / / {3}
    item instanceof Array ?
      arrReductionRecursive(item, result) / / {4}
    : 
      result.push(item); / / {5}
  })

  return result; / / {6}
}

/ / test
const arr = [[0.1], [2[4[6.7]]]];
console.log('arrReduction: ', arrReduction(arr)); // [0, 1, 2, 4, 6, 7]
Copy the code

Array/object array decrement

Implementation idea:

  • Define unique to take two parameters arr, name
  • Line {1} name must be passed if it is an array of objects to be deleted
  • The key set in line {2} is used for filtering below
  • Line {3} If name does not exist, filter as usual, and set key to current, the current element of the array
  • Line {4} checks if the key to be filtered is in the current object, and if the value is assigned to the key
  • Line {5} For object elements, if the key is not in the current object, set a random value so that other keys are not affected. For example, [{a: 1}, {b: 1}] now filters elements with key A, but there is no A in B to handle this case
  • Line {6} to solve cases like [3, ‘3’], this filters out ‘3’ as well
  • Line {7} this is the key of our implementation, if the key does not exist in the hash object do nothing, otherwise, set hash[key] = true and add elements like prev.
  • Line {8} returns the current result the next time the user traverses it
/** * array/object array deduplicates *@param { Array } Arr Array to be repealed *@param { String } Name If it is an array of objects, is the basis for filtering key *@returns { Array }* /
function unique (arr=[], name=' ') {
  if ((arr[0] instanceof Object) && !name) { / / {1}
    throw new Error('Object array please pass in attributes to filter! ');
  }

  const hash = {};
  return arr.reduce((prev, current) = > {
    let key; / / {2}
    if(! name) { key = current;/ / {3}
    } else if (current.hasOwnProperty(name)) { 
      key = current[name]; / / {4}
    } else {
      key = Math.random(); // {5} Ensure that other keys are not affected
    }

    if(! (Object.prototype.toString.call(key) === '[object Number]')) { / / {6}
      key += '_';
    }

    hash[key] ? ' ' : hash[key] = true && prev.push(current); / / {7}

    return prev; / / {8}} []); }let arr = [1.2.2.3.'3'.4];
    arr = [{ a: 1 }, { b: 2 }, { b: 2 }]
    arr = [{ a: 1 }, { a: 1 }, { b: 2 }]

console.log(unique(arr, 'a'));
Copy the code

A simpler new ES6 data structure, Set, can be used because sets guarantee that the elements in the Set are unique, but the support is limited. Object arrays are not supported

let arr = [1.2.2.3.'3'.4];
[...new Set(arr)] // [1, 2, 3, '3', 4]
Copy the code

Deep copy

The difference between the deep copy and the shallow copy is as follows: When the deep copy encounters an object or array of a complex type, the reference to the original object is cut off and copied layer by layer to ensure that the two objects do not affect each other. When the shallow copy encounters an object or array, the reference is copied.

Implementation idea:

  • Define the deepClone function to receive parameter Elements
  • Line {1} verifies that the parameter is valid
  • Line {2} defines the recursive function deepCloneRecursive which is the core implementation of deep copy
  • Line {3} creates a new object or array, opening a new storage address and disreferencing the original object
  • Line {4} traverses the object
  • Line {5} validates if it is an object or array to continue the recursive deep copy, otherwise for primitive types or functions
  • Line {6} completes the traversal, returning the newly cloned object

/** * deep copy *@param { Object|Array|Function } elements
 * @returns { Object|Array|Function } newElements* /
function deepClone(elements) {
  if(! typeCheck(elements,'Object')) { / / {1}
    throw new Error('Must be an object')}return deepCloneRecursive(elements); / / {2}
}

/** * deep copy recursive call *@param { Object|Array|Function } elements* /
function deepCloneRecursive(elements) {
  const newElements = typeCheck(elements, 'Array')? [] : {};/ / {3}

  for (let k in elements) { / / {4}
    / / {5}
    if (typeCheck(elements[k], 'Object') || typeCheck(elements[k], 'Array')) {
      newElements[k] = deepCloneRecursive(elements[k]);
    } else{ newElements[k] = elements[k]; }}return newElements; / / {6}
}

/ * * * * a similar test | the auxiliary function@param {*} Val values *@param { String } Type type *@returns { Boolean } true|false
 */
function typeCheck(val, type) {
    return Object.prototype.toString.call(val).slice(8, -1) === type;
}

/ / test
const obj = { a: 1.b: { c: [].d: function() {}}};const obj2 = deepClone(obj);

obj.b.c.push('a');
obj.b.d = [];

console.log(obj); // { a: 1, b: { c: [ 'a' ], d: [] } }
console.log(obj2); // { a: 1, b: { c: [], d: [Function: d] } }
Copy the code

Another simple approach: serialization and deserialization using JSON

const obj = { a: 1.b: { c: [].d: function() {}}};const obj2 = JSON.parse(JSON.stringify(obj));

console.log(obj); // { a: 1, b: { c: [], d: [Function: d] } }
console.log(obj2); // { a: 1, b: { c: [] } }
Copy the code

Object, array, number, or string, or false, null, or true. All other types of encodings are converted by default during parsing.

Implement a sleep function

JavaScript does not directly provide the sleep() thread sleep functionality found in Java or other languages at the language level. You might see code like this:

function sleep(seconds) { / / not desirable
  const start = new Date(a);while (new Date() - start < seconds) {}
}
sleep(10000); / / for 10 seconds
Copy the code

After running it, as shown in the figure above, the CPU explodes because JavaScript is single-threaded, so CPU resources are used to service the code. This is a blocking operation, not a thread sleep, and it also breaks the event loop scheduling, making other tasks unable to execute.

Methods a

The correct way to write this is to use setTimeout to control delayed execution.

/** * delay function *@param { Number } Seconds The unit is */
function sleep(seconds) {
  return new Promise(resolve= > {
    setTimeout(function() {
      resolve(true);
    }, seconds)
  })
}

async function test() {
  console.log('hello');
  await sleep(5000);
  console.log('world! Output after 5 seconds');
}

test();
Copy the code

Method 2

The ECMA262 draft provides the atomics. wait API to implement thread sleep, which actually blocks the event loop, blocking the thread until it times out.

Atomics.wait(Int32Array, index, value[, timeout]) verifies that the given Int32Array position still contains its value, waits to wake up or until timeout, and returns a string indicating that it has timed out or been woken up.

Also, since our business is working on the main thread, avoid using it in the main thread and use it in the node.js worker thread as needed.

/** * true blocking event loop, block thread until timeout, do not use * on main thread@param {Number} ms delay
 * @returns {String} ok|not-equal|timed-out
 */
function sleep(ms) {
  const valid = ms > 0 && ms < Infinity;
  if (valid === false) {
    if (typeofms ! = ='number' && typeofms ! = ='bigint') {
      throw TypeError('ms must be a number');
    }
    throw RangeError('ms must be a number that is greater than 0 but less than Infinity');
  }

  return Atomics.wait(int32, 0.0.Number(ms))
}

sleep(3000)
Copy the code

Methods three

Write C/C++ plug-ins through n-API, see the author of this project github.com/qufei1993/e… It also contains the implementation of the above methods.

Currization function implementation

When I first heard this name, I felt very mysterious and lofty. In fact, after I understood it, it was not so complicated. Let’s reveal what this mysterious Currie function is.

The function of receiving function as parameter is called higher order function, and Currization is a special writing method of higher order function.

Function corrification is the transformation of a function that takes multiple arguments into a new function that initially takes only one argument and returns the results of the remaining arguments.

Add (1), (2) and (3) add(1), (2) and (3) add(1), (3) add(1), (2) and (3) add(1, 2)

function add(a) {
  return function(b) {
    return function(c) {
      returna + b + c; }}}console.log(add(1) (2) (3)); / / 6
Copy the code

Function Currization is much more powerful, so we need to find a way to implement a general expression of Currization. In the example above we used closures, but the code is repetitive, so we need to implement recursively.

Implementation idea:

  • Line {1} defines the addFn function
  • Line {2} defines that the Curry Curryization function takes two arguments, the first being the function fn needs to curryize, and the second… Args is actually multiple parameters such as 1, 2…
  • Line {3} args.length is the argument passed in by the function. If it is smaller than fn.length, the desired argument length is not enough, and the recursive call continues to collect arguments
  • Line {4} is an anonymous function
  • Line {5} gets the parameters. Note that the data obtained is an array, so line {6} is deconstructed and passed
  • If args. Length > fn.length indicates that args is collected, start executing line {7} {7} fn. Call (null,… args)
  • Line {8} creates the Currified function add, which returns the result curry’s anonymous function at line {4}
  • At this point the entire function has been currified and can be tested on its own.
/** * add function *@param { Number } a 
 * @param { Number } b 
 * @param { Number } c 
 */
function addFn(a, b, c) { / / {1}
  return a + b + c;
}

/** ** The Currization function *@param { Function } fn 
 * @param { ...any } Args records the parameter */
function curry(fn, ... args) { / / {2}
  if (args.length < fn.length) { / / {3}
    return function() { / / {4}
      let _args = Array.prototype.slice.call(arguments); / / {5}
      returncurry(fn, ... args, ... _args);// {6}}return fn.apply(null, args); / / {7}
}

// The curry function is abbreviated as follows, which may be easier to understand
// const curry = (fn, ... args) => args.length < fn.length ?
/ / (... _args) => curry(fn, ... args, ... _args)
// 	:
// 	fn.call(null, ...args);

// Currize add
const add = curry(addFn); / / {8}

console.log(add(1) (2) (3)); / / 6
console.log(add(1.2) (3)); / / 6
console.log(add(1) (2.3)); / / 6
Copy the code

Implement a new/instanceof operator

Customize _new() to implement the new operator

Custom _new() method to simulate the implementation principle of the new operator, which is divided into the following 3 steps:

  • Line {1} creates a new object based on the constructor’s Prototype property
    • {1.1} Create a new object obj
    • {1.2} The proto of the new object points to the constructor’s prototype, which implements inheritance
  • Line {2} changes the this reference, passing the new instance obj and arguments to the constructor fn to execute
  • Line {3} If the constructor does not manually return an object, return the object created in the first step, for example: function Person(name) {this.name = name; return this; {2} result will return an object, otherwise result will return undefined and obj will be returned.
/** * implements a new operator *@param { Function } The fn constructor *@param  { ...any } args
 * @returns { Object } Constructor instance */
function _new(fn, ... args) {
  // {1} creates a new object based on the constructor's prototype property
  // The following two lines are equivalent to
  // const obj = Object.create(fn.prototype)
  const obj = {}; // {1.1} create a new object obj
  obj.__proto__ = fn.prototype; // {1.2} the new object's __proto__ points to the constructor's prototype, which implements inheritance

  // {2} change the this pointer to pass the new instance obj and arguments to the constructor fn
  const result = fn.apply(obj, args);

  // {3} returns the instance, or the object created in the first step if the constructor does not manually return the object
  return typeof result === 'object' ? result : obj;
}
Copy the code

Passing the constructor Person and the line argument to our custom _new() method gives instance Zhangsan. Using the instanceof symbol is the same as using new.

function Person(name, age) {
  this.name = name;
  this.age = age;
}

const zhangsan = _new(Person, 'Joe'.20);
const lisi = new Person('bill'.18)

console.log(zhangsan instanceof Person, zhangsan); // true Person {name: 'zhang3 ', age: 20}
console.log(lisi instanceof Person, lisi); // true Person {name: 'name ', age: 18}
Copy the code

Custom _instanceof() implements the instanceof operator

function Person() {}
const p1 = new Person();
const n1 = new Number(a)console.log(p1 instanceof Person) // true
console.log(n1 instanceof Person) // false

console.log(_instanceof(p1, Person)) // true
console.log(_instanceof(n1, Person)) // false

function _instanceof(L, R) {
  L = L.__proto__;
  R = R.prototype;

  while (true) {
    if (L === null) return false;
    if (L === R) return true; L = L.__proto__; }}Copy the code

Call /apply/bind three brothers

Three differences:

  • Call: change this and pass other arguments one by one, which is executed immediately, for example: test.call(obj, 1, 2);
  • Call (obj, [1, 2]); call(obj, [1, 2]);
  • Const testFn = test.bind(this, 1); const testFn = test.bind(this, 1); testFn(2);

1. Customize mayJunCall function

  • Line {1} If the context does not exist, the browser is set to window and Nodejs is set to global, depending on the environment
  • The functions defined by the line {2} context remain unique and are implemented with the ES6 Symbol method
  • Line {3} this is the method to execute, for example function test(){}; Test.call (null) This represents the test() method
  • Line {4} converts the arguments class array to an array
  • Line {5} executes the function fn
  • Line {6} remember to remove the context-bound fn function
  • Line {7} Returns the result if the function has a return value
/* * Implement a call method of your own */
Function.prototype.mayJunCall = function(context) {
  // {1} If the context does not exist, set the browser to window and Nodejs to global depending on the environment
  context = context ? context : globalThis.window ? window : global;
  const fn = Symbol(a);// The functions defined by the {2} context remain unique, using the ES6 Symbol method
  context[fn] = this; // {3} this is the method that needs to be executed, for example function test(){}; Test.call (null) This represents the test() method
  const args = [...arguments].slice(1); // {4} convert arguments to an array
  constresult = context[fn](... args)// {5} passes in arguments to execute the method
  delete context[fn]; // {6} remember to delete
  return result; // {7} If the function returns a value, the result is returned
}

/ / test
name = 'lisi';
const obj = {
  name: 'zs'
};

function test(age, sex) {
  console.log(this.name, age, sex);
}

test(18.'male'); / / 18 male lisi
test.mayJunCall(obj, 18.'male'); / / zs 18 male
Copy the code

2. Customize mayJunApply functions

The only difference is that Apply accepts an array as an argument, so it starts by checking the argument. If the argument is passed and not an array, a TypeError is raised.

/** * implement your own apply method */
Function.prototype.mayJunApply = function(context) {
  let args = [...arguments].slice(1); // Convert arguments to an array

  if (args && args.length > 0&&!Array.isArray(args[0]) {// Parameter verification, if passed must be array
    throw new TypeError('CreateListFromArrayLike called on non-object');
  }

  context = context ? context : globalThis.window ? window : global;
  const fn = Symbol(a); context[fn] =this; 
  args = args.length > 0 ? args[0] : args; [[0, 1]]
  constresult = context[fn](... args);delete context[fn];
  return result
}
Copy the code

3. Customize mayJunBind

Bind’s implementation is different from call and apply, but not as complicated. First, bind does not execute immediately after binding. Instead, it returns a new anonymous function that will only be executed if you call it manually.

In addition, bind can be divided into two receptions:

  • The first time is when you perform bind
function test(age, sex) {
  console.log(`name: The ${this.name}, age: ${age}, sex: ${sex}`);
}
const fn = test.mayJunBind(obj, 20); // Call bind
Copy the code
  • The second time was during the actual execution
fn('male') // Pass in the second argument
Copy the code

The following is the mode implementation of bind. Finally, apply is called to bind the context

/** * implement your own bind method */
Function.prototype.mayJunBind = function(context) {
  const that = this; // Save this for the current call, since bind is not executed immediately
  const firstArgs = [...arguments].slice(1); // Get the arguments for the first binding

  return function() {
    const secondArgs = [...arguments]; // Get arguments for the second execution
    const args = firstArgs.concat(secondArgs); // Double parameter stitching

    return that.apply(context, args); // Bind the function to the context, passing in the twice-fetched parameter args}}Copy the code

Realize the map/reduce

Define mayJunMap to implement the map function

The first argument to map is the callback, and the second argument is the callback's this value */
Array.prototype.mayJunMap = function(fn, thisValue) {
  const fnThis = thisValue || [];
  return this.reduce((prev, current, index, arr) = > {
    prev.push(fn.call(fnThis, current, index, arr));
    returnprev; } []); }const arr1 = [undefined.undefined];
const arr2 = [undefined.undefined].mayJunMap(Number.call, Number);
const arr3 = [undefined.undefined].mayJunMap((element, index) = > Number.call(Number, index));

// Arr2 is the same thing as arr3
console.log(arr1) // [ undefined, undefined ]
console.log(arr2) // [0, 1]
console.log(arr3) // [0, 1]
Copy the code

Define mayJunReduce to implement the Reduce function

Array.prototype.mayJunReduce = function(cb, initValue) {
  const that = this;

  for (let i=0; i<that.length; i++) {
    initValue = cb(initValue, that[i], i, that);
  }

  return initValue;
}

const arr = [1.2.3];
const arr1 = arr.mayJunReduce((prev, current) = > {
  console.log(prev, current);
  prev.push(current)
  return prev;
}, [])

console.log(arr1)
Copy the code

Handwritten Promise code

This is a classic interview question, I put it in the end, no nonsense directly on the code, a total of 5 parts to complete, the realization of the idea is as follows, clear the Promise realization principle, a lot of problems naturally solved.

Declare the MayJunPromise class

We do some initialization in the constructor

  • Line {1} initializes some default values, the state of the Promise, the value on success, and the reason for failure
  • Const p = new Promise(resolve => {setTimeout(function(){resolve(1)}, 5000)}), When resolve is in setTimeout, we call p.chen () and the state is pending, so we need a place to store it. Here is the set of callback functions used to store Promise resolve
  • Line {3} onRejectedCallbacks holds the set of Promise Reject callbacks as does line {2}
  • If {4} succeeds, the callback is irreversible. If status = pending, change the value of the status and success
  • Callback if line {5} fails, as in line {4} above, for example resolve(1); reject(‘err’); The second reject cannot be overwritten
  • Line {6} is self-executing
  • Line {7} failed to run error catch
/** * encapsulates a Promise of its own */
class MayJunPromise {
  constructor(fn) {
    // {1} initializes some defaults
    this.status = 'pending'; / / a promise one and only one state (pending | fulfilled | rejected)
    this.value = undefined; // a valid JavaScript value (including undefined, thenable, promise)
    this.reason = undefined; // is a value indicating why the promise failed
    this.onResolvedCallbacks = []; / / {2}
    this.onRejectedCallbacks = []; / / {3}

    // {4} callback successful
    let resolve = value= > {
      if (this.status === 'pending') {
        this.status = 'fulfilled'; / / final state
        this.value = value; / / final value
        this.onResolvedCallbacks.forEach(itemFn= >{ itemFn() }); }}// {5} failed callback
    let reject = reason= > {
      if (this.status === 'pending') { // The state is irreversible, such as resolve(1); reject('err'); The second reject cannot be overwritten
        this.status = 'rejected'; / / final state
        this.reason = reason; / / final value
        this.onRejectedCallbacks.forEach(itemFn= >itemFn()); }}try {
      // {6} is self-executing
      fn(resolve, reject);
    } catch(err) {
      reject(err); // {7} failed to catch}}}Copy the code

2. Then method

  • A promise must provide a THEN method to access its current value, final value, and reason
  • This is a big pity, onFulfilled and onRejected are optional. Because Promise. Then can be called chain, judge the scene where the value passes through
  • The line {9} then method must return a Promise object
  • Lines {10}, {11} and {12} are also three cases that can be realized in the THEN method. Similarly, the number of times can only be explained with the state equal to depressing
  • Line {10.1} Promise/A+ specification definition: To ensure that ondepressing and onRejected are called in the next event loop, you can use setTimeout to implement this, because I am in node.js environment, SetImmediate Therefore uses setImmediate to register events (because you can avoid the setTimeout delay)
  • Line {10.2} Promise/A+ The standard states: If ondepressing or onRejected returns an X, then it will process the resolution with [[Resolve]](promise2, x). The resolution function we define is also a core function, resolveMayJunPromise, which will be described in the following
/** * encapsulates a Promise of its own */
class MayJunPromise {.../** * A promise must provide a THEN method to access its current value, final value, and argument *@param { Function } Ondepressing is optional. If it is a function, it must be called after the state is fulfilled and accepts a parameter value *@param { Function } OnRejected is optional. If it is a function, it must be called after the rejected state and takes a parameter, Reason *@returns { Promise } The return value must be Promise */
  then(onFulfilled, onRejected) {
    // This is a big pity, onFulfilled and onRejected are optional parameters. // This is a big pity, onFulfilled and onRejected are optional parameters
    // scenario: new Promise(resolve => resolve(1)).then().then(value => console.log(value));
    onFulfilled = Object.prototype.toString.call(onFulfilled) === '[object Function]' ? onFulfilled : function(value) {return value};
    onRejected = Object.prototype.toString.call(onRejected) === '[object Function]' ? onRejected : function(reason) {throw reason};

    The {9} then method must return a Promise object
    const promise2 = new MayJunPromise((resolve, reject) = > {
      / / {10}
      if (this.status === 'fulfilled') { // This inherits the this bound by the outer context
        // {10.1} Promise/A+ stipulation: make sure ondepressing and onRejected are called in the next event loop
        // Macro tasks (setTimeout, setImmediate) or microtasks (MutationObsever, process.nexttick) can be used
        setImmediate(() = > {
          try {
            // this is A big pity or onFulfilled // this is A big pity or onRejected returns an X, then [[Resolve]](promise2, x) will be fulfilled
            const x = onFulfilled(this.value);
            // The function that resolves x is defined as resolveMayJunPromise
            resolveMayJunPromise(promise2, x, resolve, reject);
          } catch(e) { reject(e); }}); }/ / {11}
      if (this.status === 'rejected') {
        setImmediate(() = > {
          try {
            const x = onRejected(this.reason)
            resolveMayJunPromise(promise2, x, resolve, reject);
          } catch(e) { reject(e); }}); }/ / {12}
      // There are cases where the state cannot be retrieved in time and the initial value is still pending, for example:
      // return new Promise(resolve => { setTimeout(function() { resolve(1) }, 5000) })
      //	.then(result => { console.log(result) })
      if (this.status === 'pending') {
        this.onResolvedCallbacks.push(() = > {
          setImmediate(() = > {
            try {
              const x = onFulfilled(this.value);
              resolveMayJunPromise(promise2, x, resolve, reject);
            } catch(e) { reject(e); }}); });this.onRejectedCallbacks.push(() = > {
          setImmediate(() = > {
            try {
              const x = onRejected(this.reason)
              resolveMayJunPromise(promise2, x, resolve, reject);
            } catch(e) { reject(e); }}); }); }});returnpromise2; }}Copy the code

3. Promise resolution process

Declare the function resolveMayJunPromise(). The Promise resolution process is an abstract operation that interacts with the Promise of the system or some Promise implementation that follows the Promise/A+ specification. The following code is recommended for reading along with the Promise/A+ specification, which is clearly written.

Note: There are some problems with the [2.3.2] sample writing during the actual coding test. You will need to use other Promise state values to judge.

/** * Promise process *@param { Promise } promise2 
 * @param { any } x 
 * @param { Function } resolve 
 * @param { Function } reject 
 */
function resolveMayJunPromise(promise2, x, resolve, reject){
  // [2.3.1] Promise and x cannot point to the same object. TypeError is used as the basis for refusing to implement the promise, for example:
  // let p = new MayJunPromise(resolve => resolve(1))
  // let p2 = p.then(() => p2); // If you don't make a judgment, it will be in an endless loop
  if (promise2 === x) {
    return reject(new TypeError('Chaining cycle detected for promise'));
  }
  
  // [2.3.2] determine that x is a Promise instance, which can make the Promise instance from the system compatible, for example:
  // new MayJunPromise(resolve => resolve(1))
  // .then(() => new Promise( resolve => resolve(2)))
  // This discovery is also not needed because [2.3.3] is already included
  // if (x instanceof Promise) {
  // // [2.3.2.1] If x is pending, keep it (recursively execute the resolveMayJunPromise handler)
  // // until the pending status changes to depressing or Rejected
  // if (x.status === 'pending') {
  // x.then(y => {
  // resolveMayJunPromise(promise2, y, resolve, reject);
  // }, reject)
  This will be a big pity if (x.status === 'depressing ') {// [2.3.2.2
  // x.then(resolve);
  } else if (x.status === 'reject ') {// [2.3.2.3] if x is rejected, reject it
  // x.then(reject);
  / /}
  // return;
  // }

  // [2.3.3] x is an object or function, which can be compatible with the system Promise
  // new MayJunPromise(resolve => resolve(1))
  // .then(() => new Promise( resolve => resolve(2)))
  if(x ! =null && (x instanceof Promise || typeof x === 'object' || typeof x === 'function')) {
    let called = false;
    try {
      // [2.3.3.1] assigns x. teng to THEN
      // Stores a reference to x.chen to avoid multiple access to the x.chen property. This precaution ensures consistency of the property as its value may be changed during retrieval calls.
      const then = x.then;

      // [2.3.3.3] If then is a function (which defaults to a promise), x is called in the function's scope this.
      // Pass two callback functions as arguments, the first called resolvePromise and the second called rejectPromise.
      if (typeof then === 'function') {

        // then. Call (x, resolvePromise, rejectPromise) is equivalent to x. Chen (resolvePromise, rejectPromise), I understand that x, MayJunPromise's then method, will be called at this point
        then.call(x, y= > { // if resolvePromise is called with the value y, run [[Resolve]](promise, y)
            if (called) return;
            called = true;
            resolveMayJunPromise(promise2, y, resolve, reject);
        }, e= > { // [2.3.3.3.2] If rejectPromise is invoked with r, reject the promise with r
          if (called) return;
          called = true;

          reject(e);
        });
      } else {
        // [2.3.3.4] If then is not a function, execute a promise with an x argument
        resolve(x)
      }
    } catch(e) { // [2.3.3.2] If an error e is thrown when the value x.teng is set, reject a promise based on e
      if (called) return;
      called = true; reject(e); }}else{ resolve(x); }}Copy the code

4. Verify your Promise

Promise provides a test script for correctness verification.

npm i -g promises-aplus-tests
promises-aplus-tests mayjun-promise.js
Copy the code

A Deferred method also needs to be exposed.

MayJunPromise.defer = MayJunPromise.deferred = function () {
  let dfd = {}
  dfd.promise = new MayJunPromise((resolve,reject) = >{
    dfd.resolve = resolve;
    dfd.reject = reject;
  });
  return dfd;
}

module.exports = MayJunPromise;
Copy the code

5. Implement catch, resolve, Reject, all, and race methods

The Promise/A+ specification only provides then methods, but we use catch, promise.all, promise.race, etc., which can be implemented on top of THEN methods

class MayJunPromise {
  constructor(fn){... }then(){... },/** * catch error *@param { Function } onRejected 
   */
  catch(onRejected) {
    return this.then(undefined, onRejected); }}/** * this is very depressing. /** * this is very depressing
MayJunPromise.resolve = function(value) {
  return (value instanceof Promise || value instanceof MayJunPromise) ? value // If the Promise instance is returned directly
    : new MayJunPromise(resolve= > resolve(value));
}

/** * Returns only the rejected state, that is, status = rejected */
MayJunPromise.reject = function(value) {
  return (value instanceof Promise || value instanceof MayJunPromise) ? value : new MayJunPromise(reject= > reject(value));
}

/** * mayjunpromise.all () parallel execution *@param { Array } arr
 * @returns { Array }* /
MayJunPromise.all = function(arr) {
  return new MayJunPromise((resolve, reject) = > {
    const length = arr.length;
    let results = []; // Save the execution result
    let count = 0; / / counter

    for (let i=0; i<length; i++) {
      MayJunPromise.resolve(arr[i]).then(res= > {
        results[i] = res;
        count++;

        if (count === length) { // All will be fulfilled laterresolve(results); }},err= > reject(err)); // If there is only one failure, the failure result is returned}}); }/** * mayjunpromise.race (); /** * mayjunpromise.race (); * /
MayJunPromise.race = function(arr) {
  return new MayJunPromise((resolve, reject) = > {
    for (let i=0; i<arr.length; i++) {
      MayJunPromise.resolve(arr[i])
        .then(result= > resolve(result), err= >reject(err)); }})}Copy the code

6. Concurrent request control

If I want to limit the number of concurrent requests to 100, what should I do? For example, in Chrome, there is a limit of 6 concurrent links at a time, and the rest of the links must wait for one of them to complete before they can be executed. The following defines the allByLimit method to achieve a similar function.

/** * Concurrent request limit *@param { Array } Arr Array of concurrent requests *@param { Number } Limit Indicates the maximum number of concurrent requests */
MayJunPromise.allByLimit = function(arr, limit) {
  const length = arr.length;
  const requestQueue = [];
  const results = [];
  let index = 0;

  return new MayJunPromise((resolve, reject) = > {
    const requestHandler = function() {	
      console.log('Request start ', index);
      const request = arr[index]().then(res= > res, err= > {
        console.log('Error', err);

        return err;
      }).then(res= > {
        console.log('Number of concurrent requests', requestQueue.length)
        const count = results.push(res); // Save all results

        requestQueue.shift(); // Each completed request is removed from the queue

        if (count === length) { // All requests are complete, and the result is returned
          resolve(results);
        } else if (count < length && index < length - 1) {
          ++index;
          requestHandler(); // Proceed to the next request}});if(requestQueue.push(request) < limit) { ++index; requestHandler(); }}; requestHandler() }); }Copy the code

Test, define a sleep sleep function, simulate delayed execution

/** ** sleep function *@param { Number } Ms delay time | milliseconds *@param { Boolean } Flag The default value is false. If it is true, reject is returned. */
const sleep = (ms=0, flag=false) = > new Promise((resolve, reject) = > setTimeout(() = > {
  if (flag) {
    reject('Reject ' + ms);
  } else {
    resolve(ms);
  }
}, ms));

MayJunPromise.allByLimit([
  () = > sleep(5000.true),
  () = > sleep(1000),
  () = > sleep(1000),
  () = > sleep(4000),
  () = > sleep(10000)],3).then(res= > {
  console.log(res);
});

// The following is the result
Request start  0
Request start  1
Request start  2
Number of concurrent requests 3
Request start  3
Number of concurrent requests 3
Request start  4
Error Reject 5000
Number of concurrent requests 3
Number of concurrent requests 2
Number of concurrent requests 1
[ 1000.1000.'Reject 5000'.4000.10000 ]
Copy the code

7. Promise reference

  • zhuanlan.zhihu.com/p/21834559
  • Juejin. Cn/post / 684490…
  • promisesaplus.com/
  • www.ituring.com.cn/article/665…

Realization principle of CO

Co is a function that automatically triggers scheduling next

/** * define a generator function test */
function *test() {
  yield 1;
  const second = yield Promise.resolve(2);
  // console.log('second', second);
  const third = yield 3;
  // console.log('third', third);

  return 'ok! ';
}

const gen = test();
console.log(gen.next()) // { value: 1, done: false }
console.log(gen.next()) // { value: Promise { 2 }, done: false }
console.log(gen.next()) // { value: 3, done: false }
console.log(r.next()) // { value: 'ok! ', done: true }
Copy the code

A custom co function automatically triggers the next function

/** * Custom CO function implementation *@param { Generator } The gen generator function */
function mayJunCo(gen) {
  return new Promise((resolve, reject) = > {
    function fn(data) {
      const { value, done } = gen.next(data);
      
      // If done is true, the recursion ends
      if (done) return resolve(value);

      // Otherwise the recursive fn function automatically executes the iterator
      Promise.resolve(value).then(fn, reject);
    }

    return fn();
  })
}

mayJunCo(test()).then(console.log)
Copy the code

conclusion

Back to the beginning of this article: “Writing code by hand has become an important part of the Internet interview today.” Despite its frequent ridicule, it still holds true, as platforms like Niuke and LeetCode can attest.

If the interviewer can through the handwritten/machine code written interview, from the perspective of the interview that may predict the interviewer have some professional skills in the job, but it is not a completely positive answers, if only by this, maybe you will put in the wrong people, and refused to good people, like a person can be very good at, to solve the problem. But you don’t necessarily have much experience solving real problems in the real world.

As a candidate, if you fail a handwritten/computer-based coding interview, don’t let the rejection discourage you, either. It’s not a complete evaluation of your ability to do the job.

This article is “May Jun” in the daily record of some, not a day to complete, if not write so much about “handwriting/machine code” thinking, it would be more like a “ten JavaScript high frequency interview questions you don’t know? , other people’s is always other people’s, oneself also should think more, more hands-on practice. I hope these thoughts and techniques can be helpful to you.

An excellent software engineer must be good at thinking and summarizing, pay attention to the public account “May Jun” let us become an excellent software engineer in their hearts.