Because Promise.all can't be realized, an interview is cool

Posted by wesmont on Wed, 08 Dec 2021 09:26:17 +0100

preface

(ಥ﹏ಥ) the interviewer asked him to write a Promise.all about a real event that really happened to a friend. The friend didn't play well on the spot and didn't write it out. Afterwards, he asked the interviewer for a vague evaluation that the foundation was not solid enough and he didn't master the principle knowledge... Of course, this is not the only problem that failed the whole interview, There must be other reasons.

But it sounded an alarm for us: Promise handwritten implementation and Promise static method implementation have long been high-frequency questions in the interview. If you don't know much about them, it will delay you for 10 minutes. Let's work together until they understand O(∩∩) O

Common interview handwriting series

Fat head fish wants to do something recently. I hope to write the common handwritten questions in the front-end interview into a series, and try to explain the knowledge and principles involved. If you are also interested in this series, welcome to learn together. Oh, at present, 66 + handwritten questions have been realized!

1. Click to view the source code address of RI Gong I question (currently 66 + handwritten questions have been implemented)

2. Nuggets column

Promise.resolve

Brief review

  1. Promise.resolve(value) method returns a value parsed with the given value Promise   Object.
  2. If the value is a promise, the promise will be returned;
  3. If this value is thenable (i.e. with "then"  Method), the returned promise will "follow" the thenable object and adopt its final state; Otherwise, the returned promise will be completed with this value.

This is the explanation on MDN. Let's take a look one by one

  1. Promise.resolve the final result is also a promise and is closely related to the value passed in by promise.resolve (this value)
  2. The passed in parameter can be a Promise instance, and the result of this function is to return the instance directly
  3. The most important thing here is to understand the following. It can be understood that the Promise final state is the value output by the thenable object

Small example

// 1. Non Promise object, non thenable object
Promise.resolve(1).then(console.log) // 1

// 2. Promise object success status
const p2 = new Promise((resolve) => resolve(2))

Promise.resolve(p2).then(console.log) // 2

// 3. Promise object failure status
const p3 = new Promise((_, reject) => reject('err3'))

Promise.resolve(p3).catch(console.error) // err3

// 4. thenable object
const p4 = {
  then (resolve) {
    setTimeout(() => resolve(4), 1000)
  }
}
Promise.resolve(p4).then(console.log) // 4

// 5. Nothing passed
Promise.resolve().then(console.log) // undefined

Source code implementation

Promise.myResolve = function (value) {
  // It is a Promise instance. You can return it directly
  if (value && typeof value === 'object' && (value instanceof Promise)) {
    return value
  }
  // Otherwise, all other cases will be packaged by Promise 
  return new Promise((resolve) => {
    resolve(value)
  })
}

// Test it, or use the example just now
// 1. Non Promise object, non thenable object
Promise.myResolve(1).then(console.log) // 1

// 2. Promise object success status
const p2 = new Promise((resolve) => resolve(2))

Promise.myResolve(p2).then(console.log) // 2

// 3. Promise object failure status
const p3 = new Promise((_, reject) => reject('err3'))

Promise.myResolve(p3).catch(console.error) // err3

// 4. thenable object
const p4 = {
  then (resolve) {
    setTimeout(() => resolve(4), 1000)
  }
}
Promise.myResolve(p4).then(console.log) // 4

// 5. Nothing passed
Promise.myResolve().then(console.log) // undefined

doubt

From the source code implementation, I don't see the special treatment of thenable objects! In fact, it really doesn't need to be processed in Promise.resolve. The real processing place should be in the Promise constructor. If you are interested in this, you will write the Promise implementation article right away. Look forward to your reading.

Promise.reject

Brief review

The Promise.reject() method returns a Promise object with a rejection reason.

Promise.reject(new Error('fail'))
  .then(() => console.log('Resolved'), 
        (err) => console.log('Rejected', err))
// Output the following        
// Rejected Error: fail
//    at <anonymous>:2:16        

Source code implementation

The reject implementation is relatively simple, as long as a new Promise is returned and the result status is set to reject

Promise.myReject = function (value) {
  return new Promise((_, reject) => {
    reject(value)
  })
}

// Test it
Promise.myReject(new Error('fail'))
  .then(() => console.log('Resolved'), 
        (err) => console.log('Rejected', err))

// Rejected Error: fail
//    at <anonymous>:9:18

Promise.all

Brief review

The Promise.all() method is used to wrap multiple Promise instances into a new Promise instance. This static method should be the most common in an interview

const p = Promise.all([p1, p2, p3])

The final state of p is determined by p1, p2 and p3, which is divided into two cases.

(1) Only when the states of p1, p2 and p3 become fully, the state of p will become fully. At this time, the return values of p1, p2 and p3 form an array and are passed to the callback function of p.

(2) As long as one of p1, p2 and p3 is rejected, the status of p becomes rejected. At this time, the return value of the first rejected instance will be passed to p's callback function.

const p1 = Promise.resolve(1)
const p2 = new Promise((resolve) => {
  setTimeout(() => resolve(2), 1000)
})
const p3 = new Promise((resolve) => {
  setTimeout(() => resolve(3), 3000)
})

const p4 = Promise.reject('err4')
const p5 = Promise.reject('err5')
// 1. All promises succeeded
const p11 = Promise.all([ p1, p2, p3 ])
    .then(console.log) // [ 1, 2, 3 ]
      .catch(console.log)
      
// 2. One Promise failed
const p12 = Promise.all([ p1, p2, p4 ])
    .then(console.log)
      .catch(console.log) // err4
      
// 3. Two promises failed. You can see that the final output is err4, and the return value of the first failure
const p13 = Promise.all([ p1, p4, p5 ])
    .then(console.log)
      .catch(console.log) // err4

Source code implementation

Promise.myAll = (promises) => {
  return new Promise((rs, rj) => {
    // Counter
    let count = 0
    // Storage results
    let result = []
    const len = promises.length
    
    if (len === 0) {
      return rs([])
    }
    
    promises.forEach((p, i) => {
      // Note that some array items may not be Promise and need to be converted manually
      Promise.resolve(p).then((res) => {
        count += 1
        // Collect the return value of each Promise 
        result[ i ] = res
        // When all promises are successful, set the returned Promise result to result
        if (count === len) {
          rs(result)
        }
        // If one Promise catch in the listening array item fails, the Promise returned by us will also fail
      }).catch(rj)
    })
  })
}

// Test it
const p1 = Promise.resolve(1)
const p2 = new Promise((resolve) => {
  setTimeout(() => resolve(2), 1000)
})
const p3 = new Promise((resolve) => {
  setTimeout(() => resolve(3), 3000)
})

const p4 = Promise.reject('err4')
const p5 = Promise.reject('err5')
// 1. All promises succeeded
const p11 = Promise.myAll([ p1, p2, p3 ])
    .then(console.log) // [ 1, 2, 3 ]
      .catch(console.log)
      
// 2. One Promise failed
const p12 = Promise.myAll([ p1, p2, p4 ])
    .then(console.log)
      .catch(console.log) // err4
      
// 3. Two promises failed. You can see that the final output is err4, and the return value of the first failure
const p13 = Promise.myAll([ p1, p4, p5 ])
    .then(console.log)
      .catch(console.log) // err4
// It is consistent with the return of native Promise.all    

Promise.allSettled

Brief review

Sometimes, we want to wait until a group of asynchronous operations have ended, whether each operation is successful or failed, before proceeding to the next operation. Obviously, promise.all (as long as one fails, the result will enter the failed state) is not suitable, so Promise.allSettled is available

Promise.allSettled() method takes an array as a parameter. Each member of the array is a promise object and returns a new promise object. Only when the state of all promise objects in the parameter array changes (whether fully or rejected), the state of the returned promise object will change. Once the state changes, the state is always fully and will not become rejected

Let's take the above example as an example to see how it is different from Promise.all

const p1 = Promise.resolve(1)
const p2 = new Promise((resolve) => {
  setTimeout(() => resolve(2), 1000)
})
const p3 = new Promise((resolve) => {
  setTimeout(() => resolve(3), 3000)
})

const p4 = Promise.reject('err4')
const p5 = Promise.reject('err5')
// 1. All promises succeeded
const p11 = Promise.allSettled([ p1, p2, p3 ])
    .then((res) => console.log(JSON.stringify(res, null,  2)))

// output 
/*
[
  {
    "status": "fulfilled",
    "value": 1
  },
  {
    "status": "fulfilled",
    "value": 2
  },
  {
    "status": "fulfilled",
    "value": 3
  }
]
*/
      
// 2. One Promise failed
const p12 = Promise.allSettled([ p1, p2, p4 ])
    .then((res) => console.log(JSON.stringify(res, null,  2)))
        
// output 
/*
[
  {
    "status": "fulfilled",
    "value": 1
  },
  {
    "status": "fulfilled",
    "value": 2
  },
  {
    "status": "rejected",
    "reason": "err4"
  }
]
*/
      
// 3. Two promises failed
const p13 = Promise.allSettled([ p1, p4, p5 ])
    .then((res) => console.log(JSON.stringify(res, null,  2)))
        
// output 
/*
[
  {
    "status": "fulfilled",
    "value": 1
  },
  {
    "status": "rejected",
    "reason": "err4"
  },
  {
    "status": "rejected",
    "reason": "err5"
  }
]
*/

You can see:

  1. Whether it is all successful or partially failed, it will eventually enter the. then callback of Promise.allSettled
  2. In the final return value, both successful and failed items have a status attribute. The value of success is full and failure is rejected
  3. In the final return value, success contains the value attribute, while failure is the reason attribute

Source code implementation

Promise.myAllSettled = (promises) => {
  return new Promise((rs, rj) => {
    let count = 0
    let result = []
    const len = promises.length
    // If the array is empty, return empty data directly
    if (len === 0) {
      return resolve([])
    }

    promises.forEach((p, i) => {
      Promise.resolve(p).then((res) => {
        count += 1
        // Successful attribute setting 
        result[ i ] = {
          status: 'fulfilled',
          value: res
        }
        
        if (count === len) {
          rs(result)
        }
      }).catch((err) => {
        count += 1
        // Failed property settings 
        result[i] = { 
          status: 'rejected', 
          reason: err 
        }

        if (count === len) {
          rs(result)
        }
      })
    })
  })
}

// Test it
const p1 = Promise.resolve(1)
const p2 = new Promise((resolve) => {
  setTimeout(() => resolve(2), 1000)
})
const p3 = new Promise((resolve) => {
  setTimeout(() => resolve(3), 3000)
})

const p4 = Promise.reject('err4')
const p5 = Promise.reject('err5')
// 1. All promises succeeded
const p11 = Promise.myAllSettled([ p1, p2, p3 ])
    .then((res) => console.log(JSON.stringify(res, null,  2)))

// output 
/*
[
  {
    "status": "fulfilled",
    "value": 1
  },
  {
    "status": "fulfilled",
    "value": 2
  },
  {
    "status": "fulfilled",
    "value": 3
  }
]
*/
      
// 2. One Promise failed
const p12 = Promise.myAllSettled([ p1, p2, p4 ])
    .then((res) => console.log(JSON.stringify(res, null,  2)))
        
// output 
/*
[
  {
    "status": "fulfilled",
    "value": 1
  },
  {
    "status": "fulfilled",
    "value": 2
  },
  {
    "status": "rejected",
    "reason": "err4"
  }
]
*/
      
// 3. Two promises failed
const p13 = Promise.myAllSettled([ p1, p4, p5 ])
    .then((res) => console.log(JSON.stringify(res, null,  2)))
        
// output 
/*
[
  {
    "status": "fulfilled",
    "value": 1
  },
  {
    "status": "rejected",
    "reason": "err4"
  },
  {
    "status": "rejected",
    "reason": "err5"
  }
]
*/

Promise.race

Brief review

The Promise.race() method also packages multiple Promise instances into a new Promise instance.

const p = Promise.race([p1, p2, p3])

As long as one of p1, p2 and p3 takes the lead in changing the state, the state of p will change accordingly. The return value of the Promise instance that changed first is passed to the callback function of p.

const p1 = new Promise((resolve, reject) => {
  setTimeout(resolve, 500, 1)
})

const p2 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 2)
})

Promise.race([p1, p2]).then((value) => {
  console.log(value) // 2
})

Promise.race([p1, p2, 3]).then((value) => {
  console.log(value) // 3
})

Source code implementation

Smart, you must know how to implement it immediately. As long as you know which instance changes first, Promise.race will follow the result, and you can write the following code

Promise.myRace = (promises) => {
  return new Promise((rs, rj) => {
    promises.forEach((p) => {
      // Wrap p once to prevent non Promise objects
      // And align to listen, and pass the resolve and reject of the Promise we returned to p. which changes the state first, the Promise we returned will also be in what state
      Promise.resolve(p).then(rs).catch(rj)
    })
  })
}

// Test it
const p1 = new Promise((resolve, reject) => {
  setTimeout(resolve, 500, 1)
})

const p2 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 2)
})

Promise.myRace([p1, p2]).then((value) => {
  console.log(value) // 2
})

Promise.myRace([p1, p2, 3]).then((value) => {
  console.log(value) // 3
})

ending

Maybe you and I have never met, but it's probably too late to meet. hope here Can become your habitat, I am willing to harvest joy with you and go to grow.

The above is the first analysis of handwriting implementation principle! You are welcome to correct the possible errors and problems

Topics: Javascript Front-end Vue.js Promise