Take you to write a complete Promise in three minutes
Implement a foundation
Promise is a class that accepts a function parameter, and then has a status variable and then method inside. The initial value of the status state is pending. The callback function of the then method is temporarily stored in memory. After the asynchronous operation of the parameter function that needs to be executed in advance is executed, the successful execution will resolve(value), the status will change to full, and the failed execution will reject (ERR), The status changes to rejected
Based on the above analysis, you can draw out the code:
class MyPromise { constructor(fn) { this.status = 'pending' this.value = null this.err = null this.fulfilledCallback = null this.rejectedCallback = null const resolve = value => { this.status = 'fulfilled' this.value = value this.fulfilledCallback(value) } const reject = err => { this.status = 'rejected' this.err = err this.rejectedCallback(err) } try { fn(resolve, reject) } catch (err) { reject(err) } } then(success, fail) { this.fulfilledCallback = success this.rejectedCallback = fail } }
A basic Promise operation is implemented. new MyPromise can pass in an asynchronous function, and then the asynchronous operation of. Then
new MyPromise((resolve, reject) => { setTimeout(() => { console.log(12) resolve(34) }) }).then(rsp => { console.log(rsp) }) // Output 12 34
But another. then will report an error
Solve the error reporting of chain operation
However, there is only one then function in this implementation. The real promise can be followed by many then functions. On this basis, we can rewrite the then method to return this object
... then(success, fail) { if (this.status === 'fulfilled') { success(this.value) } if (this.status === 'rejected') { fail(this.err) } this.fulfilledCallback = success this.rejectedCallback = fail return this }
Upgrade success event functions and failure event functions to queues
class MyPromise { constructor(fn) { this.status = 'pending' this.fulfilledCallbacks = [] this.rejectedCallbacks = [] const resolve = value => { this.fulfilledCallbacks.forEach(cb=>{ cb(value) }) } const reject = err => { this.rejectedCallbacks.forEach(cb=>{ cb(err) }) } try { fn(resolve, reject) } catch (err) { reject(err) } } then(success, fail) { if (this.status === 'fulfilled') { this.value = success(this.value) } else if (this.status === 'rejected') { this.err = fail(this.err) } else { this.fulfilledCallbacks.push(success) this.rejectedCallbacks.push(fail) } return this } } new MyPromise((resolve, reject) => { setTimeout(() => { console.log(12) resolve(34) }) }).then(rsp => { console.log(rsp) }).then(rsp => { console.log(rsp) }) // 12 34 34
In this way, the callback functions in. then can be executed
The argument of the parameter function in the next then is the return value of the previous parameter function
Promise can not only realize asynchronous, but also complete chain operation. It can also take the return value of the previous parameter function in then as the argument of the parameter function in then
new Promise((resolve, reject) => { setTimeout(() => { resolve(1) }) }).then(rsp => { console.log(rsp) return 2 }).then(rsp => { console.log(rsp) }) // 1 2
Let's also implement it. My idea is to assign the return value of the forEach loop function to this.value after resolve or rejected. Let's try it
... const resolve = value => { this.value = value this.fulfilledCallbacks.forEach(cb=>{ this.value = cb(this.value) }) } new MyPromise((resolve, reject) => { setTimeout(() => { resolve(1) }) }).then(rsp => { console.log(rsp) return 2 }).then(rsp => { console.log(rsp) }) // 1 2
complete
Implement Promise.all
Don't say much, just go to the code
static all(list) { const results =[] return new MyPromise((resolve, reject) => { list.forEach(promise => { promise.then(rsp => { results.push(rsp) if (results.length === list.length) { resolve(results) } }) }) }) }
It can be seen from the above that when the promise in the queue executes one, it will push into the result queue. When the last resolve (the last resolve is not necessarily the last of the queue); The results are all pushed in (results.length === list.length), and the final return is the list of resolve results.
let p1 = new MyPromise((resolve)=> resolve(1)) let p2 = new MyPromise((resolve)=> { setTimeout(()=>{ resolve(2) }, 1000) }) let p3 = new MyPromise((resolve)=> { setTimeout(()=>{ resolve(3) }) }) MyPromise.all([p1, p2, p3]).then(rsp=> { console.log(rsp) }) // [1,3,2]
Implement Promise.resolve
The implementation of Promise.resolve is relatively simple
... static resolve(value) { return new MyPromise(resolve=>{ resolve(value) }) }
After testing, there is no problem
MyPromise.resolve(1).then(rsp => { console.log(rsp) }) // 1
Complete code
The whole code is as follows:
class MyPromise { constructor(fn) { this.status = 'pending' this.fulfilledCallbacks = [] this.rejectedCallbacks = [] const resolve = value => { this.status = 'fulfilled' this.value = value this.fulfilledCallbacks.forEach(cb=>{ this.value = cb(this.value) }) } const reject = err => { this.status = 'rejected' this.err = err this.rejectedCallbacks.forEach(cb=>{ this.value = cb(this.err) }) } try { fn(resolve, reject) } catch (err) { reject(err) } } then(success, fail) { if (this.status === 'fulfilled') { this.value = success(this.value) } else if (this.status === 'rejected') { this.err = fail(this.err) } else { this.fulfilledCallbacks.push(success) this.rejectedCallbacks.push(fail) } return this } static all(list) { const results =[] return new MyPromise((resolve, reject) => { list.forEach(promise => { promise.then(rsp => { results.push(rsp) if (results.length === list.length) { resolve(results) } }) }) }) } static resolve(value) { return new MyPromise(resolve=>{ resolve(value) }) } }
Summary and discussion
We only wrote some functions that Promise uses relatively more, and there is a micro task simulation that has not been implemented. As far as we know, the resolve function is asynchronous when it is executed. The implementation principle is similar to node.js process.nextTick. This involves the bottom layer, which we will not discuss here