Skip to content

Commit

Permalink
add promise
Browse files Browse the repository at this point in the history
  • Loading branch information
amandakelake committed Mar 30, 2018
1 parent 7c65e99 commit 14d95c3
Show file tree
Hide file tree
Showing 2 changed files with 177 additions and 1 deletion.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
.idea
test.js
177 changes: 176 additions & 1 deletion JS/JS-en.md
Original file line number Diff line number Diff line change
Expand Up @@ -213,4 +213,179 @@ Each function, besides `Function.prototype.bind()` , has an internal property, d

Each object has an internal property, denoted as `__proto__` , which is a reference to the prototype of the constructor that created the object. This property actually references to `[[prototype]]` , but `[[prototype]]` is an internal property that we can’t access, so we use `__proto__` to access it.

Objects can use `__proto__` to find properties that do not belong to the object, and `__proto__` connects objects together to form a prototype chain.
Objects can use `__proto__` to find properties that do not belong to the object, and `__proto__` connects objects together to form a prototype chain.



#### Promise implementation

`Promise` is a new syntax introduced by ES6, which resolves the problem of callback hell. In this section I will explain the principle through the Promise / A+ specification.

```js
// promise accepts a function argument that will execute immediately.
function MyPromise(executor) {
let _this = this;
// A promise must be in one of three states: `pending`, `fulfilled`, or `rejected`
// Besides `pending`, the other two states must not change
_this.status = 'pending';
// To save the callback of `then`, but also resolve the executor's asynchronous executions: `resolve`,`reject`
_this.onResolvedCallback = [];
_this.onRejectedCallback = [];

// The following two functions are specified by the specification
function resolve(value) {
if (_this.status === 'pending') {
_this.status = 'resolved';
_this.value = value;
_this.onResolvedCallback.forEach(fn => fn());
}
}

function reject(reason) {
if (_this.status === 'pending') {
_this.status = 'rejected';
_this.reason = reason;
_this.onRejectedCallback.forEach(fn => fn());
}
}

// Used to solve the following problem
// new Promise(() => throw Error('error))
try {
executor(resolve, reject);
} catch (e) {
reject(e);
}
}
// specification 2.3
function resolvePromise(promise2, x, resolve, reject) {
// specification 2.3.1
// `x` can't refer to the same object that `promise2` refers to, avoiding the circular references
if (promise2 === x) {
return reject(new TypeError('Error'));
}
// specification 2.3.3.3.3
// if both `reject` and `resolve` are executed, the first successful execution takes precedence, and any further executions are ignored
let called;
// specification 2.3.2
// if `x` is a Promise and the state is `pending`, the promise must remain, If not, it should execute.
if (x instanceof Promise) {
if (x.status === 'pending') {
x.then(function(value) {
resolvePromise(promise2, value, resolve, reject);
}, reject);
} else {
x.then(resolve, reject);
}
return;
}
// specification 2.3.3, determine whether `x` is an object or a function
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
// specification 2.3.3.2, if can't get `then`, execute the `reject`
try {
// specification 2.3.3.1
let then = x.then;
// if `then` is a function, call the `x.then`
if (typeof then === 'function') {
// specification 2.3.3.3
then.call(
x,
function(y) {
if (called) return;
called = true;
// specification 2.3.3.3.1
resolvePromise(promise2, y, resolve, reject);
},
function(err) {
if (called) return;
called = true;
reject(err);
}
);
} else {
// specification 2.3.3.4
resolve(x);
}
} catch (e) {
if (called) return;
called = true;
reject(e);
}
} else {
// specification 2.3.4, `x` belongs to primitive data type
resolve(x);
}
}

MyPromise.prototype.then = function(onFulfilled, onRjected) {
// specification 2.2.1, both `onFulfilled` and `onRejected` are optional arguments
// it should be ignored if `onFulfilled` or `onRjected` is not a function, which implements the penetrate pass of it's value
// Promise.resolve(4).then().then((value) => console.log(value))
onFulfilled =
typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRjected =
typeof onRjected === 'function'
? onRjected
: error => {
throw error;
};
let _this = this;
// specification 2.2.7, `then` must return a new promise
let promise2;
if (_this.status === 'pending') {
// when the state is `pending`, push the callback
// specification 2.2.4, wrap them with `setTimeout`, in order to insure that `onFulfilled` and `onRjected` execute asynchronously,
promise2 = new MyPromise((resolve, reject) => {
_this.onResolvedCallback.push(function() {
setTimeout(() => {
// Considering that it may throw error, wrap them with `try/catch`
try {
let x = onFulfilled(_this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
});
_this.onRejectedCallback.push(() => {
setTimeout(() => {
try {
let x = onRjected(_this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
});
});
}
// The following logic is basically the same as `pending`
if (_this.status === 'resolved') {
promise2 = new MyPromise((resolve, reject) => {
setTimeout(() => {
try {
let x = onFulfilled(_this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
});
}
if (_this.status === 'rejected') {
promise2 = new MyPromise((resolve, reject) => {
setTimeout(() => {
try {
let x = onRjected(_this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
});
}
return promise2;
};
```

The above codes, which is implemented based on the Promise / A+ specification, can pass the full test of `promises-aplus-tests`

0 comments on commit 14d95c3

Please sign in to comment.