I'm trying to implement a simple promise class with chainable .then()
functionality in javascript. Here's what I've done till now -
class APromise {
constructor(Fn) {
this.value = null;
Fn(resolved => { this.value = resolved; });
}
then(fn) {
fn(this.value);
return this;
}
}
function myFun() {
return new APromise(resolve => {
// just resolve('Hello'); works but this doesn't
setTimeout(() => { resolve('Hello'); }, 2000);
});
}
const log = v => { console.log(v); };
myFun().then(log).then(log);
This outputs -
null
null
Instead of 'Hello'
2 times. I think it is currently ignoring setTimeout()
call, how should I make this work?
Your code isn't working the way you want because you are mixing async flow with sync flow.
When you call .then()
, it will return this
synchronously. Since setTimeout()
is an async function that is called after some time (2 secs), this.value
is still null
.
If you want to know more about the asynchronous flow of JS, I recommend watching this video . It's a bit long but super helpful.
Since we can't tell when setTimeout()
will call the function passed to it, we cannot call and callback functions that depend on its operations. We store these callbacks in an array for later consumption.
When the setTimeout()
function is called (promise resolves), we have the result of promise resolution. And so, we now call all the bound callbacks.
class APromise {
constructor(Fn) {
this.value = null;
- Fn(resolved => { this.value = resolved; });
+ this.callbacks = [];
+ Fn(resolved => {
+ this.value = resolved;
+
+ this.callbacks.forEach(cb => {
+ cb(this.value);
+ });
+ });
}
then(fn) {
- fn(this.value);
+ this.callbacks.push(fn);
return this;
}
}
function myFun() {
return new APromise(resolve => {
setTimeout(() => { resolve('Hello'); }, 2000);
});
}
const log = v => { console.log(v); };
myFun().then(log).then(log);
The code above solves the problem partially.
True chaining is achieved when the result of one callback is passed on to the next. That's not the case in our current code. TO make that happen, each .then(cb)
must return a new APromise
that resolves when the cb
function is called.
A complete and Promises/A+ conformant implementation is way over the scope of a single SO answer, but that shouldn't give the impression that it isn't doable. Here's a curated list of custom implentations .
Let's start with a clean slate. We need a class Promise
that implements a method then
which also returns a promise to allow chaining.
class Promise {
constructor(main) {
// ...
}
then(cb) {
// ...
}
}
Here, main
is a function that takes a function as an argument and calls it when the promise is resolved/fulfilled - we call this method resolve()
. The said function resolve()
is implemented and provided by our Promise
class.
function main(resolve) {
// ...
resolve(/* resolve value */);
}
The basic feature of the then()
method is to trigger/activate the provided callback function cb()
with the promise value, once the promise fulfills.
Taking these 2 things into account, we can rewire our Promise
class.
class Promise {
constructor(main) {
this.value = undefined;
this.callbacks = [];
const resolve = resolveValue => {
this.value = resolveValue;
this.triggerCallbacks();
};
main(resolve);
}
then(cb) {
this.callbacks.push(cb);
}
triggerCallbacks() {
this.callbacks.forEach(cb => {
cb(this.value);
});
}
}
We can test our current code with a tester()
function.
(function tester() {
const p = new Promise(resolve => {
setTimeout(() => resolve(123), 1000);
});
const p1 = p.then(x => console.log(x));
const p2 = p.then(x => setTimeout(() => console.log(x), 1000));
})();
// 123 <delayed by 1 second>
// 123 <delayed by 1 more second>
This concludes our base. We can now implement chaining. The biggest problem we face is the the then()
method must return a promise synchronously which will be resolved asynchronously .
We need to wait for the parent promise to resolve before we can resolve the next promise . This implies that instead of adding cb()
to parent promise , we must add the resolve()
method of next promise which uses return value of cb()
as its resolveValue
.
then(cb) {
- this.callbacks.push(cb);
+ const next = new Promise(resolve => {
+ this.callbacks.push(x => resolve(cb(x)));
+ });
+
+ return next;
}
If this last bit confuses you, here are some pointers:
Promise
constructor takes in a function main()
as an argument main()
takes a function resolve()
as an argument
resolve()
is provided by the Promise
constructor resolve()
takes an argument of any type as the resolveValue
class Promise {
constructor(main) {
this.value = undefined;
this.callbacks = [];
const resolve = resolveValue => {
this.value = resolveValue;
this.triggerCallbacks();
};
main(resolve);
}
then(cb) {
const next = new Promise(resolve => {
this.callbacks.push(x => resolve(cb(x)));
});
return next;
}
triggerCallbacks() {
this.callbacks.forEach(cb => {
cb(this.value);
});
}
}
(function tester() {
const p = new Promise(resolve => {
setTimeout(() => resolve(123), 1000);
});
const p1 = p.then(x => console.log(x));
const p2 = p.then(x => setTimeout(() => console.log(x), 1000));
const p3 = p2.then(x => setTimeout(() => console.log(x), 100));
const p4 = p.then((x) => new Promise(resolve => {
setTimeout(() => resolve(x), 1000);
}))
/*
p: resolve after (1s) with resolveValue = 123
p1: resolve after (0s) after p resolved with resolveValue = undefined
p2: resolve after (0s) after p resolved with resolveValue = timeoutID
p3: resolve after (0s) after p2 resolved with resolveValue = timeoutID
p4: resolve after (1s) after p resolved with resolveValue = Promise instance
*/
})();
// 123 <delayed by 1s>
// 2 <delayed by 1.1s>
// 123 <delayed by 2s>
I was using Promise
in client js that figured out in old browsers this class doesn't exist. So that I implemented one for them that have resolve
and reject
methods.
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
var Promise = function () {
function Promise(main) {
var _this = this;
_classCallCheck(this, Promise);
this.value = undefined;
this.callbacks = [];
var resolve = function resolve(resolveValue) {
_this.value = resolveValue;
_this.triggerCallbacks();
};
var reject = function reject(rejectValue) {
_this.value = rejectValue;
_this.triggerCallbacks();
};
main(resolve, reject);
}
Promise.prototype.then = function then(cb) {
var _this2 = this;
var next = new Promise(function (resolve) {
_this2.callbacks.push(function (x) {
return resolve(cb(x));
});
});
return next;
};
Promise.prototype.catch = function catch_(cb) {
var _this2 = this;
var next = new Promise(function (reject) {
_this2.callbacks.push(function (x) {
return reject(cb(x));
});
});
return next;
};
Promise.prototype.triggerCallbacks = function triggerCallbacks() {
var _this3 = this;
this.callbacks.forEach(function (cb) {
cb(_this3.value);
});
};
return Promise;
}();
Solve it, when call sync in Promise:
class MyPromise{
constructor(fn){
this.callback= null;
this.data= null;
this.calledInNext= false;
fn((data, state)=>{ // unsafe when call resolve({}, 'string')
this.calledInNext= (state === 'CALLED_IN_NEXT') ? true : false;
this.data= data;
if(this.callback) this.callback(this.data);
}, function(_err){
console.log('resolve({error}) to without catch')
})
}
then(cb){ // next
if(this.data || this.calledInNext){
return new MyPromise(r => {
r(cb(this.data), 'CALLED_IN_NEXT');
});
} else {
return new MyPromise(r => {
this.callback = x=> r(cb(x))
})
}
}
}
Or chain:
class MyPromise{
constructor(fn){
this.callbacks= [];
this.data= null;
fn((data)=>{
this.data= data;
var gg= this.data;
this.callbacks.forEach(el=>{
gg= el(gg);
})
})
}
then(cb){
if(this.data || this._calledInNext){
this._calledInNext= true; this.data= cb(this.data); return this;
} else {
this.callbacks.push(cb); return this;
}
}
}
Test:
(new MyPromise(function(resolve, reject){
// setTimeout(resolve, 1000, {done: 1})
resolve({done: 1})
})).then(data=>{
console.log(data); // {done: 1}
return data;
}).then(data=>{
console.log(data); // {done: 1}
return {};
}).then(data=>{
console.log(data); // {}
}).then(data=>{
console.log(data); // undefine
}).then(data=>{
console.log(data); // undefine
}).then(data=>{
console.log(data); // undefine
})
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.