# Promise实现

问题介绍下promise 的特性、优缺点,内部是如何实现的,动手实现Promise?????

# 基本用法

MDN Promise 对象用于表示一个异步操作的最终完成 (或失败)及其结果值

const promise = new Promise((resolve,reject)=>{
	//...
  if(/*异步操作成功*/){
    resolve(); 
   }else{
  	reject();
  }
})
promise.then((value)=>{
  //回调处理
}).catch((err)=>{
	//错误处理
})
promise.then();promise.catch();promise.finally();
Promise.reslove();Promise.reject();
Promise.all();Promise.race();Promise.allSettled();
//提案
Promise.try();Promise.any();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 特性

Promise是一种异步解决方案,主要是为了解决原本回调方式带来的地狱回调问题;

  1. Promise有三个状态(pending、resolved、rejected),状态不受外界影响只由异步操作结果控制,状态一旦改变就不会再变;
  2. Promise.prototype.then() Promise.prototype.catch() 返回一个新的promise,所以可以链式调用,将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数

# 优缺点

优点:

  • 链式调用解决了地狱回调问题,程序流程清晰
  • 一整套接口,可以实现许多强大的功能
  • 为多个回调函数中抛出的错误,统一制定处理方法

缺点:

  • 无法取消,一旦新建就会立即执行,无法中途取消;
  • 当处于pending状态时,无法得知目前进展到哪一个阶段(刚开始还是快结束)
  • 如果不设置回调函数,内部错误无法反应到外部

# 实现

# 1. 基本结构

  • 设定三个状态 PENDING、FULFILLED、REJECTED ,只能由PENDING改变为FULFILLED、REJECTED,并且只能改变一次
  • MyPromise接收一个函数executorexecutor有两个参数resolve方法和reject方法
  • resolvePENDING改变为FULFILLED
  • rejectPENDING改变为FULFILLED
  • promise变为FULFILLED状态后具有一个唯一的value
  • promise变为REJECTED状态后具有一个唯一的reason
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

function MyPromise(executor) {
    this.state = PENDING;
    this.value = null;
    this.reason = null;

    const resolve = (value) => {
        if (this.state === PENDING) {
            this.state = FULFILLED;
            this.value = value;
        }
    }

    const reject = (reason) => {
        if (this.state === PENDING) {
            this.state = REJECTED;
            this.reason = reason;
        }
    }

    try {
        executor(resolve, reject);
    } catch (reason) {
        reject(reason);
    }
}
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

# 2. then方法

  • then方法接受两个参数onFulfilled、onRejected,它们分别在状态由PENDING改变为FULFILLED、REJECTED后调用
  • 一个promise可绑定多个then方法
  • then方法可以同步调用也可以异步调用
  • 同步调用:状态已经改变,直接调用onFulfilled方法
  • 异步调用:状态还是PENDING,将onFulfilled、onRejected分别加入两个函数数组onFulfilledCallbacks、onRejectedCallbacks,当异步调用resolvereject时,将两个数组中绑定的事件循环执行。
function MyPromise(executor) {
    this.state = PENDING;
    this.value = null;
    this.reason = null;
    this.onFulfilledCallbacks = [];
    this.onRejectedCallbacks = [];

    const resolve = (value) => {
      if (this.state === PENDING) {
        this.state = FULFILLED;
        this.value = value;
        this.onFulfilledCallbacks.forEach(fun => {
          fun();
        });
      }
    }

    const reject = (reason) => {
      if (this.state === PENDING) {
        this.state = REJECTED;
        this.reason = reason;
        this.onRejectedCallbacks.forEach(fun => {
          fun();
        });
      }
    }

    try {
      executor(resolve, reject);
    } catch (reason) {
      reject(reason);
    }
  }

  MyPromise.prototype.then = function (onFulfilled, onRejected) {
    switch (this.state) {
      case FULFILLED:
        onFulfilled(this.value);
        break;
      case REJECTED:
        onFulfilled(this.value);
        break;
      case PENDING:
        this.onFulfilledCallbacks.push(() => {
          onFulfilled(this.value);
        })
        this.onRejectedCallbacks.push(() => {
          onRejected(this.reason);
        })
        break;
    }
  }
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

# 3. then异步调用

虽然resolve是同步执行的,我们必须保证then是异步调用的,我们用settimeout来模拟异步调用(并不能实现微任务和宏任务的执行机制,只是保证异步调用)

MyPromise.prototype.then = function (onFulfilled, onRejected) {
    if (typeof onFulfilled != 'function') {
        onFulfilled = function (value) {
            return value;
        }
    }
    if (typeof onRejected != 'function') {
        onRejected = function (reason) {
            throw reason;
        }
    }
    switch (this.state) {
        case FULFILLED:
            setTimeout(() => {
                onFulfilled(this.value);
            }, 0);
            break;
        case REJECTED:
            setTimeout(() => {
                onRejected(this.reason);
            }, 0);
            break;
        case PENDING:
            this.onFulfilledCallbacks.push(() => {
                setTimeout(() => {
                    onFulfilled(this.value);
                }, 0);
            })
            this.onRejectedCallbacks.push(() => {
                setTimeout(() => {
                    onRejected(this.reason);
                }, 0);
            })
            break;
    }
}
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

# 4. then链式调用

保证链式调用,即then方法中要返回一个新的promise,并将then方法的返回值进行resolve

MyPromise.prototype.then = function (onFulfilled, onRejected) {
      if (typeof onFulfilled != 'function') {
        onFulfilled = function (value) {
          return value;
        }
      }
      if (typeof onRejected != 'function') {
        onRejected = function (reason) {
          throw reason;
        }
      }
      const promise2 = new MyPromise((resolve, reject) => {
        switch (this.state) {
          case FULFILLED:
            setTimeout(() => {
              try {
                const x = onFulfilled(this.value);
                resolve(x);
              } catch (reason) {
                reject(reason);
              }
            }, 0);
            break;
          case REJECTED:
            setTimeout(() => {
              try {
                const x = onRejected(this.reason);
                resolve(x);
              } catch (reason) {
                reject(reason);
              }
            }, 0);
            break;
          case PENDING:
            this.onFulfilledCallbacks.push(() => {
              setTimeout(() => {
                try {
                  const x = onFulfilled(this.value);
                  resolve(x);
                } catch (reason) {
                  reject(reason);
                }
              }, 0);
            })
            this.onRejectedCallbacks.push(() => {
              setTimeout(() => {
                try {
                  const x = onRejected(this.reason);
                  resolve(x);
                } catch (reason) {
                  reject(reason);
                }
              }, 0);
            })
            break;
        }
      })
      return promise2;
    }
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

# 5. 完整版

# 拓展

# 取消拓展

<button id="start">Start</button>
<button id="cancel">Cancel</button>
 <script>
 //取消拓展
        class CancelToken {
            constructor(cancelFn) {
                this.promise = new Promise((resolve, reject) => {
                    cancelFn(() => {
                        setTimeout(console.log, 0, "delay cancelled9999");
                        resolve();
                    });
                });
            }
        }        
        const startButton = document.querySelector('#start');
        const cancelButton = document.querySelector('#cancel');
        function cancellableDelayedResolve(delay) {
            setTimeout(console.log, 0, "set delay");
            return new Promise((resolve, reject) => {
                //原本延时定时器
                const id = setTimeout((() => {
                    setTimeout(console.log, 0, "delayed resolve");
                    resolve();
                }), delay);
                //生成cancelToken实例
                const cancelToken = new CancelToken((cancelCallback) => {
                    cancelButton.addEventListener("click", cancelCallback)
                });
                cancelToken.promise.then(() => clearTimeout(id));
            });
        }
        startButton.addEventListener("click", () => cancellableDelayedResolve(3000)); 
  </script>
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

# 进度通知拓展

//进度通知拓展
class TrackablePromise extends Promise {
    constructor(executor) {
        const notifyHandlers = [];
        super((resolve, reject) => {
            return executor(resolve, reject, (status) => {
                notifyHandlers.map((handler) => handler(status));
            });
        });
        this.notifyHandlers = notifyHandlers;
    }
    notify(notifyHandler) {
        this.notifyHandlers.push(notifyHandler);
        return this;
    }
}
let p = new TrackablePromise((resolve, reject, notify) => {
    function countdown(x) {
        if (x > 0) {
            notify(`${20 * x}% remaining`);
            setTimeout(() => countdown(x - 1), 1000);
        } else {
            resolve();
        }
    }
    countdown(5);
});
p.notify((x) => setTimeout(console.log, 0, 'progress:', x));
p.then(() => setTimeout(console.log, 0, 'completed'));

p.notify((x) => setTimeout(console.log, 0, 'a:', x))
    .notify((x) => setTimeout(console.log, 0, 'b:', x));
p.then(() => setTimeout(console.log, 0, 'completed'));
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
Last Updated: 10/29/2020, 1:21:25 AM