In my last article, I focused on the knowledge of async. There was little mention of promise. Now many interviews also require us to build wheels manually. So I will explore the characteristics of Promise by implementing a promise manually.
Simple Promise
First of all, we should know that Promise is created by a constructor (new Promise (executor) and passes parameters to the executor function:
function Promi(executor) { executor(resolve, reject); function resolve() {} function reject() {} }
Let's talk about the three states of Promise: pending-waiting, resolving-success, reject ing-failure. The first one is pending, and once it succeeds or fails, the state of Promise will not change, so according to this point:
function Promi(executor) { let _this = this; _this.$$status = 'pending'; executor(resolve.bind(this), reject.bind(this)); function resolve() { if (_this.$$status === 'pending') { _this.$$status = 'full' } } function reject() { if (_this.$$status === 'pending') { _this.$$status = 'fail' } } }
Among them, $status is used to record the status of Promise. Only when the status of promise is not pending will we change its status to'full'or'fail', because we use this in the two status functions, apparently using some properties of Promise, so we will bind this in resolution and reject for the currently created Proise.
So our most basic Proise is done (head without limbs...).
Promise Advanced - > then.
Next, all Promise instances can use the. then method, where the. then two parameters, the successful callback and the failed callback, are what we call resolve and reject:
function Promi(executor) { let _this = this; _this.$$status = 'pending'; _this.failCallBack = undefined; _this.successCallback = undefined; _this.error = undefined; executor(resolve.bind(_this), reject.bind(_this)); function resolve(params) { if (_this.$$status === 'pending') { _this.$$status = 'success' _this.successCallback(params) } } function reject(params) { if (_this.$$status === 'pending') { _this.$$status = 'fail' _this.failCallBack(params) } } } Promi.prototype.then = function(full, fail) { this.successCallback = full this.failCallBack = fail }; // Test code new Promi(function(res, rej) { setTimeout(_ => res('Success'), 30) }).then(res => console.log(res))
Let's talk about this:
You can see that we have added failCallBack and successCallback to store our callbacks in the then. As we said earlier, there can be a successful and a failed callback in the then. When the state of P becomes resolve, the successful callback is executed, and when the state of P becomes reject or error, the failed callback is executed, but the specific execution conclusion is made. Fruit control is not here. But we knew we would call one of them.
The executor mission is sure to have a successful outcome, and if it fails, we will surely get the reason for the failure. So we can pass this result or error reason through params (of course, params here can also be disassembled and assigned to Promise instances). In fact, if it's an interview question, it's basically passed, and nobody will let you complete it.
Error: Used to store, pass reject information and error information
Promise Advancement
I think we are most fascinated by Promise's chain invocation, because the most important meaning of its appearance is to make our callback look less hell (because I mentioned earlier that async is more direct than it). So why can the then chain invocation? The n must return an object that also has the then method
I think you can all guess that. The return of that must be a promise, so there will be an interesting question here. Is the return of that a new promise or a chain head caller??????????
At first glance in the code, Promise.then(...).catch(...) looks like a series of method chain calls to the original Promise object.
In fact, however, both the then and catch method calls return a new promise object. Prove it simply and forcefully
var beginPromise = new Promise(function (resolve) { resolve(100); }); var thenPromise = beginPromise.then(function (value) { console.log(value); }); var catchPromise = thenPromise.catch(function (error) { console.error(error); }); console.log(beginPromise !== thenPromise); // => true console.log(thenPromise !== catchPromise);// => true
Obviously promise returns a new one, not a caller.
But then the difficulty will come. Let's look at the following code:
function begin() { return new Promise(resolve => { setTimeout(_ => resolve('first') , 2000) }) } begin().then(data => { console.log(data) return new Promise(resolve => { }) }).then(res => { console.log(res) });
We know that the function parameter in the last one will never be executed. Why is it difficult? Think about it, the reason why chain call can be made is that a new promise is returned after execution of. then(). It must be noted that my new promise is returned by then() instead of data => return new Promise. (This is just a parameter of the n.) The problem arises. From what we have just seen, we know that only when the state in the first. then changes will the function parameters in the second then be executed. In the program, that is to say, the first. then returns the promise state change! ___________ Namely:
begin().then(data => { console.log(data) return new Promise(resolve => { setTimeout(_ => resolve('two'), 1000) }) }).then(res => { console.log(res) });
From the point of view of code, the promise state returned by the first. then() after calling the resolve in the function parameter of the first. then() has also changed. This sentence is somewhat circumvented. Let me use a graph to say:
So the question arises. How can we make P2 status change to notify P1?
In fact, it is possible to use the observer mode here, but the cost is a bit high. In other words, we can't directly make the resolve in P2 equal to the resolve in P1. So the synchronized P1 in P2 after calling the resolve is also equivalent to the onresolve. The code is as follows:
function Promi(executor) { let _this = this; _this.$$status = 'pending'; _this.failCallBack = undefined; _this.successCallback = undefined; _this.result = undefined; _this.error = undefined; setTimeout(_ => { executor(_this.resolve.bind(_this), _this.reject.bind(_this)); }) } Promi.prototype.then = function(full, fail) { let newPromi = new Promi(_ => {}); this.successCallback = full; this.failCallBack = fail; this.successDefer = newPromi.resolve.bind(newPromi); this.failDefer = newPromi.reject.bind(newPromi); return newPromi }; Promi.prototype.resolve = function(params) { let _this = this; if (_this.$$status === 'pending') { _this.$$status = 'success'; if (!_this.successCallback) return; let result = _this.successCallback(params); if (result && result instanceof Promi) { result.then(_this.successDefer, _this.failDefer); return '' } _this.successDefer(result) } } Promi.prototype.reject = function(params) { let _this = this; if (_this.$$status === 'pending') { _this.$$status = 'fail'; if (!_this.failCallBack) return; let result = _this.failCallBack(params); if (result && result instanceof Promi) { result.then(_this.successDefer, _this.failDefer); return '' } _this.successDefer(result) } } // Test code new Promi(function(res, rej) { setTimeout(_ => res('Success'), 500) }).then(res => { console.log(res); return 'First.then Success' }).then(res => { console.log(res); return new Promi(function(resolve) { setTimeout(_ => resolve('The second.then Success'), 500) }) }).then(res => { console.log(res) return new Promi(function(resolve, reject) { setTimeout(_ => reject('The third failure'), 1000) }) }).then(res => {res console.log(res) }, rej => console.log(rej));
Promise Perfection
In fact, we still have a lot to do here, such as error handling, reject processing, catch implementation,.All implementation,.Race implementation, in fact, the principle is similar, (all and race and resolve and reject actually return a new Promise), error transmission? There are many details we have not considered, I write here. One is fairly perfect:
function Promi(executor) { let _this = this; _this.$$status = 'pending'; _this.failCallBack = undefined; _this.successCallback = undefined; _this.error = undefined; setTimeout(_ => { try { executor(_this.onResolve.bind(_this), _this.onReject.bind(_this)) } catch (e) { _this.error = e; if (_this.callBackDefer && _this.callBackDefer.fail) { _this.callBackDefer.fail(e) } else if (_this._catch) { _this._catch(e) } else { throw new Error('un catch') } } }) } Promi.prototype = { constructor: Promi, onResolve: function(params) { if (this.$$status === 'pending') { this.$$status = 'success'; this.resolve(params) } }, resolve: function(params) { let _this = this; let successCallback = _this.successCallback; if (successCallback) { _this.defer(successCallback.bind(_this, params)); } }, defer: function(callBack) { let _this = this; let result; let defer = _this.callBackDefer.success; if (_this.$$status === 'fail' && !_this.catchErrorFunc) { defer = _this.callBackDefer.fail; } try { result = callBack(); } catch (e) { result = e; defer = _this.callBackDefer.fail; } if (result && result instanceof Promi) { result.then(_this.callBackDefer.success, _this.callBackDefer.fail); return ''; } defer(result) }, onReject: function(error) { if (this.$$status === 'pending') { this.$$status = 'fail'; this.reject(error) } }, reject: function(error) { let _this = this; _this.error = error; let failCallBack = _this.failCallBack; let _catch = _this._catch; if (failCallBack) { _this.defer(failCallBack.bind(_this, error)); } else if (_catch) { _catch(error) } else { setTimeout(_ => { throw new Error('un catch promise') }, 0) } }, then: function(success = () => {}, fail) { let _this = this; let resetFail = e => e; if (fail) { resetFail = fail; _this.catchErrorFunc = true; } let newPromise = new Promi(_ => {}); _this.callBackDefer = { success: newPromise.onResolve.bind(newPromise), fail: newPromise.onReject.bind(newPromise) }; _this.successCallback = success; _this.failCallBack = resetFail; return newPromise }, catch: function(catchCallBack = () => {}) { this._catch = catchCallBack } }; // Test code task() .then(res => { console.log('1:' + res) return 'First then' }) .then(res => { return new Promi(res => { setTimeout(_ => res('The second then'), 3000) }) }).then(res => { console.log(res) }) .then(res => { return new Promi((suc, fail) => { setTimeout(_ => { fail('then fail') }, 400) }) }) .then(res => { console.log(iko) }) .then(_ => {}, () => { return new Promi(function(res, rej) { setTimeout(_ => rej('promise reject'), 3000) }) }) .then() .then() .then(_ => {}, rej => { console.log(rej); return rej + 'Processing completed' }) .then(res => { console.log(res); // Deliberate error console.log(ppppppp) }) .then(res => {}, rej => { console.log(rej); // Throw again console.log(oooooo) }).catch(e => { console.log(e) })
There is also a piece of code that I implemented by returning all.then to the caller. That is, I used a promise to record the state storage task queue all the way. It is not sent out here. I am interested in discussing it together.
There will be time to improve all, race, resolve.... But then the code structure will definitely change. There is really not much time, so pay attention to it, welcome to exchange.