JavaScript 中的 Promise 详解以及手写一个 Promise

1 Javascript单线程

Javascript的单线程,与它的用途有关。作为浏览器脚本语言,Javascript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定Javascript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?
为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。

2 同步和异步

2.1 同步任务

同步任务是指在主线程上排队执行的任务,只有前一个任务执行完毕,才能继续执行下一个任务。

2.2 异步任务

异步任务是指不进入主线程,而进入任务队列的任务,只有任务队列通知主线程,某个异步任务可以执行了,该任务才会进入主线程。

3 异步执行的运行机制

3.1
所有同步任务都在主线程上执行,形成一个执行栈。

3.2
主线程之外,还存在一个"任务队列"。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。

3.3
一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行

3.4
主线程不断重复上面的第三步。

4 promise是什么

4.1
主要用于异步计算。

4.2
可以将异步操作队列化,按照期望的顺序执行,返回符合预期的结果。

4.3
可以再对象之间传递和操作promise,帮助我们处理队列。

5 为什么要用promise

5.1
支持链式调用,可以解决回调地狱问题。

5.2
指定回调函数的方式更加灵活。

6 resolve()函数 / reject()函数

6.1
如果调用resolve()函数和reject()函数时带有参数,那么它们的参数会被传递给回调函数。reject()函数的参数通常是Error对象的实例,表示抛出的错误;resolve()函数的参数除了正常的值以外,还可能是另一个 Promise 实例。

const p1 = new Promise((resolve, reject) => {
  setTimeout(reject, 3000, '这是第一个promise');
});
const p2 = new Promise((resolve, reject) => {
  setTimeout(p1, 1000);
});
p2.then(res => {
  console.log(res);
}).catch(err => {
  console.log(err);
})

注意,此时p1的状态会传递给p2,也就是说,p1的状态决定了p2的状态。如果p1的状态是pending,那么p2的回调函数就会等待p1的状态改变;如果p1的状态已经是resolved或者rejected,那么p2的回调函数将会立刻执行。
上面代码中,p1是一个Promise,3秒之后变为rejected。p2的状态在1秒之后改变,resolve方法返回的是p1。由于p2返回的是另一个Promise,导致p2自己的状态无效了,由p1的状态决定p2的状态。所以,后面的then语句都变成针对后者(p1)。又过了2秒,p1变为rejected,导致触发catch()方法指定的回调函数。

6.2
注意,调用resolve()或reject()并不会终结Promise的参数函数的执行。但是,如果调用 resolve() 或 reject() 后再继续调用 resolve() 或 reject() 是不会执行的。

new Promise((resolve, reject) => {
  resolve(1);
  console.log(2);
  reject(3);	// 不执行
}).then(res => {
  console.log(res);
}).catch(err => {
  console.log(err);
})
// 2
// 1

6.3
一般来说,调用resolve或reject以后,Promise的使命就完成了,后继操作应该放到then方法里面,而不应该直接写在resolve或reject的后面。所以,最好在它们前面加上return语句,这样就不会有意外。

7 then()方法

7.1
then()方法是定义在原型对象Promise.prototype上的。其作用是为Promise实例添加状态改变时的回调函数。

7.2
then()方法返回一个新的状态为resolve的promise实例,根据前面的promise状态,选择相应的状态响应函数执行。

7.3
状态响应函数可以返回新的promise,也可以返回其他值,也可以不返回值。

7.4
如果当前then()方法返回新的promise,那么下一级then()方法会在新的promise状态改变后执行。

7.5
如果当前then()方法返回的是其他值,那么会立即执行下一级then()方法,且其res值为返回值。

7.6
如果当前then()方法没有返回值,那么会立即执行下一级then()方法,且其res值为undefined。

8 catch()方法

8.1
Promise.prototype.catch()方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数。

8.2
如果将promise处理函数从

const p = new Promise((resolve, reject) => {
  ...
}).then(res => {}, err => {})

改为

const p = new Promise((resolve, reject) => {
  ...
}).then(res => {})
  .catch(err => {})

那么如果前面的promise是reject状态,其就会进入catch()方法而非then()方法。catch()方法基本和then()方法一致。
另外,then()方法指定的回调函数,如果运行中抛出错误,也会被catch()方法捕获。

8.3
一般来说,不要在 then() 方法里面定义 reject 状态的回调函数(即 then 的第二个参数),总是使用 catch 方法。理由是第二种写法可以捕获前面 then 方法执行中的错误,也更接近同步的写法(try / catch)。因此,建议总是使用 catch() 方法,而不使用 then() 方法的第二个参数。

9 finally() 方法

finally() 方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。

9.1
finally 方法的回调函数不接受任何参数,这意味着没有办法知道,前面的 Promise 状态到底是 fulfilled 还是 rejected。所以 finally 方法里面的操作,应该是与状态无关的,不依赖于 Promise 的执行结果。

10 Promise.all()方法

10.1
Promise.all()用于将多个promise实例,包装成一个新的Promise实例,返回的实例就是普通的promise。

10.2
其接收的参数为可迭代对象,可迭代对象的元素可以为promise,也可以为其他值,如果为其他值,就会先调用 Promise.resolve 方法,将该参数转为 Promise 实例,再进一步处理。

10.3
当可迭代对象中所有的子promise都完成,该promise完成,返回值是全部promise的结果和非promise的值的数组。

const p1 = new Promise((resolve, reject) => {
  resolve('第一个promise');
});
const p2 = new Promise((resolve, reject) => {
  resolve('第二个promise');
});

// promiseAll为一个新的promise
const promiseAll = Promise.all([p1, p2, '这不是promise']);
promiseAll.then(res => {
  console.log(res);	// ['第一个promise', '第二个promise', '这不是promise']
}).catch(err => {
  console.log(err);
})

10.4
有任何一个失败,该promise失败,返回值是数组中第一个失败的promise结果。

const p1 = new Promise((resolve, reject) => {
  resolve('第一个promise');
});
const p2 = new Promise((resolve, reject) => {
  resolve('第二个promise');
});
const p3 = new Promise((resolve, reject) => {
  reject('第三个promise');
});
const p4 = new Promise((resolve, reject) => {
  reject('第四个promise');
});

// promiseAll为一个新的promise
const promiseAll = Promise.all([p1, p2, p3, p4, '这不是promise']);
promiseAll.then(res => {
  console.log(res);
}).catch(err => {
  console.log(err);	// '第三个promise'
})

11 promise.race()方法

11.1
Promise.race() 方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。

11.2
其接收的参数为可迭代对象,可迭代对象的元素可以为promise,也可以为其他值,如果为其他值,就会先调用 Promise.resolve 方法,将该参数转为 Promise 实例,再进一步处理。

11.3
当可迭代对象中所有某一个 promise 完成,该 promise 完成,返回值是第一个完成的 promise 的结果。

12 手写 Promise

class myPromise {
  constructor(executor) {
    const self = this;
    self.value = null;
    self.status = 'pending';
    self.onFulfilledCallbacks = [];
    self.onRejectedCallbacks = [];

    const resolve = function (value) {
      setTimeout(() => {
        if (self.status == 'pending') {
          self.value = value;
          self.status = 'fulfilled';
          self.onFulfilledCallbacks.forEach(item => {
            item(value);
          })
        }
      });
    }

    const reject = function (reason) {
      setTimeout(() => {
        if (self.status == 'pending') {
          self.value = reason;
          self.status = 'rejected';
          self.onRejectedCallbacks.forEach(item => {
            item(reason);
          })
        }
      });
    }

    executor(resolve, reject);
  }

  then(onFulfilled, onRejected) {
    if (typeof onFulfilled != 'function') {
      onFulfilled = value => value;
    }
    if (typeof onRejected != 'function') {
      onRejected = reason => {
        throw reason;
      }
    }

    const promise2 = new myPromise((resolve, reject) => {
      if (this.status == 'fulfilled') {
        setTimeout(() => {
          try {
            let x = onFulfilled(this.value);
            resolvePromise(promise2, x, resolve, reject);
          } catch (error) {
            reject(error);
          }
        });
      }

      if (this.status == 'rejected') {
        setTimeout(() => {
          try {
            let x = onRejected(this.value);
            resolvePromise(promise2, x, resolve, reject);
          } catch (error) {
            reject(error);
          }
        });
      }

      if (this.status == 'pending') {
        this.onFulfilledCallbacks.push((value) => {
          setTimeout(() => {
            try {
              let x = onFulfilled(value);
              resolvePromise(promise2, x, resolve, reject);
            } catch (error) {
              reject(error);
            }
          });
        })

        this.onRejectedCallbacks.push((value) => {
          setTimeout(() => {
            try {
              let x = onRejected(value);
              resolvePromise(promise2, x, resolve, reject);
            } catch (error) {
              reject(error);
            }
          });
        })
      }
    })

    return promise2;
  }
}

const resolvePromise = function (promise2, x, resolve, reject) {
  if (promise2 == x) {
    return reject(new Error('Chaining cycle detected for promise'));
  }

  let called = false;
  if (x != null && (typeof x == 'function' || typeof x == 'object')) {
    try {
      const then = x.then;
      if (typeof then == 'function') {
        then.call(x, y => {
          if (called) {
            return;
          }
          called = true;
          resolvePromise(promise2, y, resolve, reject);
        }, err => {
          if (called) {
            return;
          }
          called = true;
          reject(err);
        })
      } else {
        resolve(x);
      }
    } catch (error) {
      if (called) {
        return;
      }
      called = true;
      reject(error);
    }
  } else {
    resolve(x);
  }
}
posted @ 2021-03-17 15:13  LittleJuliet  阅读(291)  评论(0)    收藏  举报