Realize a Promise

Realize a Promise according to Promises/A+.

Basic

  • new Promise instance
  • execute the function and execute the callback function according to the result
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function MyPromise(fn) {
let self = this; // store the current promise instance
self.value = null; // success value
self.error = null; // error reason
self.onFulfilled = null; // success callback
self.onRejected = null; // fail callback

function resolve(value) {
self.value = value;
self.onFulfilled(self.value);
}

function reject(error) {
self.error = error;
self.onRejected(self.error);
}
fn(resolve, reject);
}
MyPromise.prototype.then = function(onFulfilled, onRejected) {
// register the success callback and fail callback
this.onFulfilled = onFulfilled;
this.onRejected = onRejected;
};
module.exports = MyPromise;

Support Sync Task

1
2
3
4
5
6
7
8
9
10
11
12
13
function resolve(value) {
setTimeout(() => {
self.value = value;
self.onFulfilled(self.value);
}, 0);
}

function reject(error) {
setTimeout(() => {
self.error = error;
self.onRejected(self.error);
}, 0);
}

Support Three Status

  • realize three status: pending, fulfilled, rejected
  • realize two changes: pending to fulfilled, pending to rejected
  • once the status changed, add then will get the result directly
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";

function MyPromise(fn) {
let self = this; // store the current promise instance
self.value = null; // success value
self.error = null; // error reason
self.onFulfilled = null; // success callback
self.onRejected = null; // fail callback
self.status = PENDING;

function resolve(value) {
setTimeout(() => {
// only if status is pending, we can change it to fulfilled and execute the success callback
if (self.status === PENDING) {
self.status = FULFILLED;
self.value = value;
self.onFulfilled(self.value);
}
}, 0);
}

function reject(error) {
setTimeout(() => {
// only if status is pending, we can change it to rejected and execute the fail callback
if (self.status === PENDING) {
self.status = REJECTED;
self.error = error;
self.onRejected(self.error);
}
}, 0);
}
fn(resolve, reject);
}
MyPromise.prototype.then = function(onFulfilled, onRejected) {
if (this.status === PENDING) {
// register the success callback and fail callback
this.onFulfilled = onFulfilled;
this.onRejected = onRejected;
} else if (this.status === FULFILLED) {
// if status is fulfilled, execute the success callback with the value
onFulfilled(this.value);
} else {
// if status is rejected, execute the fail callback with the error
onRejected(this.error);
}
};
module.exports = MyPromise;

Support Chain Operation

use Array to store callback

1
2
self.onFulfilledCallbacks = [];
self.onRejectedCallbacks = [];

traverse array to execute callback

1
self.onFulfilledCallbacks.forEach((callback) => callback(self.value));

return this in then()

1
2
3
4
5
6
7
8
9
10
11
MyPromise.prototype.then = function(onFulfilled, onRejected) {
if (this.status === PENDING) {
this.onFulfilledCallbacks.push(onFulfilled);
this.onRejectedCallbacks.push(onRejected);
} else if (this.status === FULFILLED) {
onFulfilled(this.value)
} else {
onRejected(this.error)
}
return this;
}

Support Serial Asynchronous Operation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86

// resolve fulfilled return x, x could be a promise object or a value
function resolvePromise(bridgePromise, x, resolve, reject) {
if (x instanceof MyPromise) {
if (x.status === PENDING) {
x.then(
y => {
resolvePromise(bridgePromise, y, resolve, reject);
},
error => {
reject(error);
}
);
} else {
x.then(resolve, reject);
}
} else {
resolve(x);
}
}

MyPromise.prototype.then = function(onFulfilled, onRejected) {
const self = this;
// use bridgePromise to link up subsequent operations
let bridgePromise;
// set a default value
onFulfilled =
typeof onFulfilled === "function" ? onFulfilled : value => value;
// set a default error
onRejected =
typeof onRejected === "function"
? onRejected
: error => {
throw error;
};
if (self.status === FULFILLED) {
return (bridgePromise = new MyPromise((resolve, reject) => {
setTimeout(() => {
try {
let x = onFulfilled(self.value);
// next promise resolve previous fulfilled return
resolvePromise(bridgePromise, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
}));
}
if (self.status === REJECTED) {
return (bridgePromise = new MyPromise((resolve, reject) => {
setTimeout(() => {
try {
let x = onRejected(self.error);
resolvePromise(bridgePromise, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
}));
}
if (self.status === PENDING) {
// when use resolve/rejected async, store onFulfilled/onRejected to the callback array
return (bridgePromise = new MyPromise((resolve, reject) => {
self.onFulfilled.push(value => {
try {
let x = onFulfilled(value);
resolvePromise(bridgePromise, x, resolve, reject);
} catch (e) {
reject(e);
}
});
self.onRejected.push(error => {
try {
let x = onRejected(error);
resolvePromise(bridgePromise, x, resolve, reject);
} catch (e) {
reject(e);
}
});
}));
}
};

MyPromise.prototype.catch = function(onRejected) {
return this.then(null, onRejected);
};

Promise/A+ Test

Promises/A++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
function resolvePromise(bridgePromise, x, resolve, reject) {
//2.3.1 avoid circular reference
if (bridgePromise === x) {
return reject(new TypeError("Circular reference"));
}
let called = false;
if (x instanceof MyPromise) {
// this branch is same with the next because promise is a thenable object, can delete
if (x.status === PENDING) {
x.then(
y => {
resolvePromise(bridgePromise, y, resolve, reject);
},
error => {
reject(error);
}
);
} else {
x.then(resolve, reject);
}
} else if (x != null && (typeof x === "object" || typeof x === "function")) {
try {
// if x is a thenable object
//2.3.3.1 let then be x.then
let then = x.then;
if (typeof then === "function") {
//2.3.3.3 If then is a function, call it with x as this, first argument resolvePromise, and second argument rejectPromise.
then.call(
x,
y => {
if (called) {
return;
}
called = true;
resolvePromise(bridgePromise, y, resolve, reject);
},
error => {
if (called) {
return;
}
called = true;
reject(error);
}
);
} else {
//2.3.3.4 If then is not a function, fulfill promise with x.
resolve(x);
}
} catch (e) {
//2.3.3.2 If retrieving the property x.then results in a thrown exception e, reject promise with e as the reason.
if (called) {
return;
}
called = true;
reject(e);
}
} else {
resolve(x);
}
}

all,race,resolve,reject

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
MyPromise.all = function(promises) {
return new MyPromise(function(resolve, reject) {
let result = [];
let count = 0;
for (let i = 0; i < promises.length; i++) {
promises[i].then(function(data) {
result[i] = data;
if (++count == promises.length) {
resolve(result);
}
}, function(error) {
reject(error);
});
}
});
}

MyPromise.race = function(promises) {
return new MyPromise(function(resolve, reject) {
for (let i = 0; i < promises.length; i++) {
promises[i].then(function(data) {
resolve(data);
}, function(error) {
reject(error);
});
}
});
}

MyPromise.resolve = function(value) {
return new MyPromise(resolve => {
resolve(value);
});
}

MyPromise.reject = function(error) {
return new MyPromise((resolve, reject) => {
reject(error);
});
}

Code

The whole code can be found in here

Reference