We’re used to promises, but how does it work? So let’s analyze it.
According to Teacher Ruan YifengIntroduction to ES6″, for the Promise. As we can see, promises have two major features:
Based on our use of promises and features, we concluded the following points:
1. Promise is an asynchronous operation.
2. Promise has three states (pending, depressing and Rejected). The initial state is pending, and the other two states are the result states.
3. If the state has changed, then the result is returned immediately.
4. The Promise constructor accepts a function that takes resolve and reject.
5. Promise uses then to add multiple callback functions that trigger the execution of those state callbacks when the state changes.
6. Then takes two arguments, one for a successful callback and one for a failed callback, and the value of the resulting state goes into the callback.
7. Promises catch internal exceptions.
Through the above summary, we can preliminarily realize a Promise skeleton.
const STATUS = Object.freeze({ // Define three states
PENDING: "pending".FULFILLED: "fulfilled".REJECTED: "rejected"
})
class MyPromise {
_status = STATUS.PENDING; // Initial state
_value = undefined; // Execution result
_fulfilledFun = []; // Depressing state corresponds to the task queue to be implemented
_rejectedFun = [] // Rejected Indicates the task queue
constructor(handle) {
if (!this._isFunction(handle)) {
throw TypeError('arguments must be a function')}try { // Promises catch internal execution exceptions
handle(this._resolve.bind(this), this._reject.bind(this)) // The Promise constructor callback takes two arguments
} catch (err) {
this._reject(err)
}
}
_isFunction(handle) {
return typeof handle === 'function'
}
_run(tasks) {
let cb;
while (cb = tasks.shift()) {
cb()
}
}
_resolve(val) {
if (this._status ! == STATUS.PENDING)return;
this._status = STATUS.FULFILLED;
this._value = val;
setTimeout(() = > { /// In order to simulate asyncracy and achieve the same asyncracy effect as promise, this is actually a macro queue, not a real Promise microqueue
this._run(this._fulfilledFun)
}, 0)}_reject(val) {
if (this._status ! == STATUS.PENDING)return;
this._status = STATUS.REJECTED;
this._value = val;
setTimeout(() = > {
this._run(this._rejectedFun)
}, 0)}then(resolve, reject) {
const { _status, _value } = this;
switch (_status) {
// Add multiple callbacks to the corresponding status task queue
case STATUS.PENDING:
this._fulfilledFun.push(resolve)
this._rejectedFun.push(reject)
break;
// Return the result immediately if the status changes
case STATUS.FULFILLED:
resolve(_value)
break;
case STATUS.REJECTED:
reject(_value)
break; }}}var p = new MyPromise((res, rej) = > {
res(3333)
})
p.then((res) = > {
console.log(1, res)
})
console.log(1)
// Execution result
/ / 1
/ / 1, 3333
Copy the code
Above we implemented a basic Promise skeleton.
As you can see in the figure, then returns a Promise instance, but we did not return anything from the previous step, so we need to modify then and extend then.
class MyPromise {
/ /...
// Is used to process the return value of then
_handleCheck(preTask, nextTask, nextResolve, nextReject) {
let result = this._value;
try {
if (this._isFunction(preTask)) {
result = preTask(result)
if (result instanceof MyPromise) { // If then returns a promise, the next chained call uses the return value of the PROMISE in then
result.then(nextResolve, nextReject)
} else {
nextTask(result)
}
} else { // If then is not a function, then the next chained call to then takes the value received by then
nextTask(result)
}
} catch (err) {
nextReject(err)
}
}
then(resolve, reject) {
const { _status } = this;
return new MyPromise((nextResolve, nextReject) = > {
// Queue preprocessing, which is used to determine the result of then, in order to ensure that the next level of the chain call is passed correctly
const _fulfilledHandle = this._handleCheck.bind(this, resolve, nextResolve, nextResolve, nextReject)
const _rejectedHandle = this._handleCheck.bind(this, reject, nextReject, nextResolve, nextReject)
switch (_status) {
case STATUS.PENDING:
this._fulfilledFun.push(_fulfilledHandle)
this._rejectedFun.push(_rejectedHandle)
break;
case STATUS.FULFILLED:
_fulfilledHandle()
break;
case STATUS.REJECTED:
_rejectedHandle()
break; }}}})var p = new MyPromise((res, rej) = > {
res(3333)
})
p.then((res) = > {
console.log(1, res)
return 67
}).then((res) = > {
console.log(5, res)
})
/** * Result 1 1 3333 5 67 */
Copy the code
Such a basic core function has been implemented. Promise has a few other methods that we can implement with these basic features.
1, catch
catch(reject) {
return this.then(undefined, reject)
}
Copy the code
2, the finally
finally(callback) {
return this.then(
value= > MyPromise.resolve(callback()).then(() = > value),
error= > MyPromise.resolve(callback()).then(() = > {throw error})
)
}
Copy the code
3, all
static all(list) {
return new MyPromise((res, rej) = > {
const values = [];
const size = list.length;
const count = 0;
for(let [index, p] of list.entries() ){
MyPromise.resolve(p).then(v= > {
values[index] = v;
count++;
if(count === size) res(values)
}).catch(err= > rej(err))
}
})
}
Copy the code
4, race
static race(list) {
return new MyPromise((res, rej) = > {
for(let p of list) {
// Use myPromise.resolve instead of direct p.chen in case p is not a normal promise
MyPromise.resolve(p).then(result= > {
res(result)
}, error= > rej(error))
}
})
}
Copy the code
5, allSettled
static allSettled(list) {
const values = [];
const size = list.length;
let count = 0;
return new MyPromise((res, rej) = > {
const getValues = (val, index) = > {
values[index] = val;
count++;
if(count === size) res(values)
}
for(let [index, p] of list.entries()) {
MyPromise.resolve(p).then(result= > {
getValues(result, index)
}, error= > getValues(error, index))
}
})
}
Copy the code
6, any
static any(list) {
const values = [];
const size = list.length;
const count = 0;
return new MyPromise((res, rej) = > {
for(let [index, p] of list.entries()) {
MyPromise.resolve(p).then(result= > {
res(result)
}, error= > {
values[index] = error;
count++;
if(count === size) rej(values)
})
}
})
}
Copy the code
Below is the complete code implementation.
const STATUS = Object.freeze({
PENDING: "pending".FULFILLED: "fulfilled".REJECTED: "rejected"
})
class MyPromise {
_status = STATUS.PENDING;
_value = undefined;
_fulfilledFun = [];
_rejectedFun = []
constructor(handle) {
if (!this._isFunction(handle)) {
throw TypeError('arguments must be a function')}try {
handle(this._resolve.bind(this), this._reject.bind(this))}catch (err) {
this._reject(err)
}
}
_isFunction(handle) {
return typeof handle === 'function'
}
_run(tasks) {
let cb;
while (cb = tasks.shift()) {
cb()
}
}
_resolve(val) {
if (this._status ! == STATUS.PENDING)return;
this._status = STATUS.FULFILLED;
this._value = val;
setTimeout(() = > { /// In order to simulate asyncracy and achieve the same asyncracy effect as promise, this is actually a macro queue, not a real Promise microqueue
this._run(this._fulfilledFun)
}, 0)}_reject(val) {
if (this._status ! == STATUS.PENDING)return;
this._status = STATUS.REJECTED;
this._value = val;
setTimeout(() = > {
this._run(this._rejectedFun)
}, 0)}_handleCheck(preTask, nextTask, nextResolve, nextReject) {
let result = this._value;
try {
if (this._isFunction(preTask)) {
result = preTask(result)
if (result instanceof MyPromise) {
result.then(nextResolve, nextReject)
} else {
nextTask(result)
}
} else {
nextTask(result)
}
} catch (err) {
nextReject(err)
}
}
then(resolve, reject) {
const { _status } = this;
return new MyPromise((nextResolve, nextReject) = > {
const _fulfilledHandle = this._handleCheck.bind(this, resolve, nextResolve, nextResolve, nextReject)
const _rejectedHandle = this._handleCheck.bind(this, reject, nextReject, nextResolve, nextReject)
switch (_status) {
case STATUS.PENDING:
this._fulfilledFun.push(_fulfilledHandle)
this._rejectedFun.push(_rejectedHandle)
break;
case STATUS.FULFILLED:
_fulfilledHandle()
break;
case STATUS.REJECTED:
_rejectedHandle()
break; }})}catch(reject) {
return this.then(undefined, reject)
}
finally(callback) {
return this.then(
value= > MyPromise.resolve(callback()).then(() = > value),
error= > MyPromise.resolve(callback()).then(() = > {throw error})
)
}
static resolve(value) {
if (value instanceof MyPromise) return value;
return new MyPromise(resolve= > resolve(value))
}
static reject(value) {
if (value instanceof MyPromise) return value;
return new MyPromise((resolve, reject) = > reject(value))
}
static all(list) {
return new MyPromise((res, rej) = > {
const values = [];
const size = list.length;
const count = 0;
for(let [index, p] of list.entries() ){
MyPromise.resolve(p).then(v= > {
values[index] = v;
count++;
if(count === size) res(values)
}).catch(err= > rej(err))
}
})
}
static race(list) {
return new MyPromise((res, rej) = > {
for(let p of list) {
// Use myPromise.resolve instead of direct p.chen in case p is not a normal promise
MyPromise.resolve(p).then(result= > {
res(result)
}, error= > rej(error))
}
})
}
static allSettled(list) {
const values = [];
const size = list.length;
let count = 0;
return new MyPromise((res, rej) = > {
const getValues = (val, index) = > {
values[index] = val;
count++;
if(count === size) res(values)
}
for(let [index, p] of list.entries()) {
MyPromise.resolve(p).then(result= > {
getValues(result, index)
}, error= > getValues(error, index))
}
})
}
static any(list) {
const values = [];
const size = list.length;
const count = 0;
return new MyPromise((res, rej) = > {
for(let [index, p] of list.entries()) {
MyPromise.resolve(p).then(result= > {
res(result)
}, error= > {
values[index] = error;
count++;
if(count === size) rej(values)
})
}
})
}
}
Copy the code