Promise

Promise 构造函数

const executor = (resolve, reject) {
  if (条件为真) {
    resolve()
  } else {
    reject()
  }
}
const promiseInstance = new Promise(executor)
1
2
3
4
5
6
7
8

通过new Promise(executor)创建 Promise 实例时,需要给Promise构造函数传递一函数作为参数,该函数有两个参数,分别是resolvereject,调用resolvereject将 Promise 实例的状态改变为resolvedrejected状态。

resolve 的参数是 Promise 实例

在创建promiseA实例的构造函数的参数fn执行过程中,若调用resolve时传入的参数是另一个 Promise 实例promiseB,则此时promiseB的状态将传递给promiseA,也就是说,promiseB的状态决定了promiseA的状态。

如果promiseB的状态是pending,那么promiseA就会等待promiseB的状态改变。

如果promiseB的状态是resolved/rejected,那么promiseA的状态也会变成resolved/rejected

var promiseB = new Promise(function (resolve, reject) {
  setTimeout(() => reject(new Error('fail')), 3000)
})

var promiseA = new Promise(function (resolve, reject) {
  setTimeout(() => resolve(promiseB), 1000)
})

promiseA.then(result => {
  console.log(result)
}).catch(error => {
  console.log(error)
})
// 执行结果:
// Error: fail
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

警告

注意,上述只是针对调用resolve时传入的参数是 Promise 实例来说的,调用reject时传入的参数是 Promise 实例则无类似效果,会直接将promiseA的状态改变为Rejected

var promiseB = new Promise(function (resolve, reject) {
  setTimeout(() => resolve('promiseB resolved!'), 3000)
})

var promiseA = new Promise(function (resolve, reject) {
  setTimeout(() => reject(promiseB), 1000)
})

promiseA.then(result => {
  console.log('then')
}).catch(error => {
  console.log('error 是否是 promise 实例:', error instanceof Promise)
})

// 执行结果:
// error 是否是 promise 实例: true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

Promise.prototype.then/catch 方法

then/catch 方法返回新的 Promise 实例

Promise 实例的then/catch方法返回一个新的 Promise 实例,因此可以采用链式写法,即then/catch方法后面再调用另一个then/catch方法。以then方法为例:

function getJSON(data) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(data)
    }, 100)
  })
}

getJSON("/posts.json").then(
  json => {
    console.log('第一个 resolved 状态的回调函数,其参数为: ' + json)
    return 'hello';
  }
).then(
  param => {
    console.log('第二个 resolved 状态的回调函数,其参数为: ' + param)
  }
)

// 执行结果:
// "第一个 resolved 状态的回调函数,其参数为: /posts.json"
// "第二个 resolved 状态的回调函数,其参数为: hello"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

上面的代码链式调用了两次then方法,并分别指定了两个resolved状态的回调函数,第一个then方法的resolved回调函数完成之后,会将返回结果作为参数,传入第二个then方法的resolved回调函数。

resolved/rejected 回调函数返回另一 Promise 实例

如果第一个then方法添加的resolved/rejected回调函数执行后return的是另一个 Promise 实例,这时第二个then添加的回调函数就会等待这个返回的 Promise 实例的状态发生变化后才会被调用,如果返回的 Promise 实例的状态变为rejected,则调用rejected回调函数,变为resolved则调用resolved回调函数。

function getJSON(data) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(data)
    }, 100)
  })
}

function getNone(data) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject(data)
    }, 100)
  })
}

getJSON("/posts.json").then(
  data => {
    console.log('第一个 then 方法添加的 resolved 回调函数: ' + data)
    return getNone('reject啦~');
  }
).then(
  data => {
    console.log('第二个 then 方法添加的 resolved 回调函数: ' + data)
  },
  err => {
    console.log('err:' + err)
  }
)
// 执行结果:
// "第一个 then 方法添加的 resolved 回调函数: /posts.json"
// "err:reject啦~"

getNone("none~").then(
  json => {
    console.log('第一个 then 方法添加的 resolved 回调函数: ' + data)
    return getNone('reject啦~');
  },
  err => {
    console.log('第一个 then 方法添加的 rejected 回调函数:' + err)
    return getNone('none~~~222')
  }
).then(
  param => {
    console.log('第二个回调函数的参数: ' + param)
  },
  err => {
    console.log('第二个 then 方法添加的 rejected 回调函数:' + err)
  }
)
// 执行结果:
// "第一个 then 方法添加的 rejected 回调函数:none~"
// "第二个 then 方法添加的 rejected 回调函数:none~~~222"
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

Promise.prototype.catch

Promise.prototype.catch(rejected)方法是Promise.prototype.then(null, rejected)的别名。

如果异步操作执行抛错,即fn函数执行时抛错,Promise 实例的状态就会变为 Rejected。

// 写法一
var promise = new Promise(function(resolve, reject) {
  throw new Error('test');
}).catch(function(error) {
  console.log(error);
});

// 写法二
var promise = new Promise(function(resolve, reject) {
  try {
    throw new Error('test');
  } catch(e) {
    reject(e);
  }
}).catch(function(error) {
  console.log(error);
});

// 写法三
var promise = new Promise(function(resolve, reject) {
  reject(new Error('test'));
}).catch(function(error) {
  console.log(error);
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

比较上面几种写法,可以发现reject函数的作用,等同于抛出错误。

如果 Promise 状态已经变成resolved,再抛出错误是无效的。

var promise = new Promise(function(resolve, reject) {
  resolve('ok');
  console.log('resolve 之后还会执行')
  throw new Error('test');
  console.log('但是抛错及以后代码都不会执行 ')
}).then(function(value) { console.log(value) })
  .catch(function(error) { console.log(error) });
1
2
3
4
5
6
7

一般来说,不要在then方法里面定义rejected回调函数(即then的第二个参数),总是使用catch方法。

// bad
promise.then(function(data) {
  // success
}, function(err) {
  // error
});

// good
promise.then(function(data) {
  // success
}).catch(function(err) {
  // error
});

1
2
3
4
5
6
7
8
9
10
11
12
13
14

跟传统的try/catch代码块不同的是,如果没有使用catch方法指定错误处理的rejected回调函数,fn抛出的错误不会传递到外层代码,即不会有任何反应。

var someAsyncThing = function() {
  return new Promise(function fn(resolve, reject) {
    // 下面一行会报错,因为x没有声明
    resolve(x + 2);
  });
};

someAsyncThing().then(function() {
  console.log('everything is great');
});

setTimeout(() => { console.log(123) }, 2000);
// Uncaught (in promise) ReferenceError: x is not defined
// 123
1
2
3
4
5
6
7
8
9
10
11
12
13
14

上面代码中,someAsyncThing函数里的fn函数执行时会报错,但是由于没有指定catch方法,这个错误不会被捕获,也不会传递到外层代码。正常情况下,运行后不会有任何输出,但是浏览器此时会打印出错误ReferenceError: x is not defined,但是不会退出进程、终止脚本执行,2 秒之后还是会输出123。这就是说,Promise 内部的错误不会影响到 Promise 外部的代码,通俗的说法就是“Promise 会吃掉错误”。

这个脚本放在服务器执行,退出码就是0(即表示执行成功)。不过,Node 有一个unhandledRejection事件,专门监听未捕获的reject错误,上面的脚本会触发这个事件的监听函数,可以在监听函数里面抛出错误。

process.on('unhandledRejection', function (err, p) {
  throw err;
});
1
2
3

上面代码中,unhandledRejection事件的监听函数有两个参数,第一个是错误对象,第二个是报错的 Promise 实例,它可以用来了解发生错误的环境信息。

then/catch 返回的 Promise 实例的状态

then/catch里的resolved/rejected回调函数执行如果不抛错,则返回的 Promise 实例的状态变为resolved,回调函数的返回值(没有return则为undefined)作为参数;抛错,状态变为rejected

var promise = new Promise(function(resolve, reject) {
  resolve('ok');
}).then(function(data) {
  console.log(data)
}).then(function(data) {
  console.log(data)
});
// 执行结果
// "ok"
// undefined

var promise = new Promise(function(resolve, reject) {
  reject('err');
}).then(
  value => console.log(value),
  err => console.log(err)
).then(
  value => console.log(value)
)
// 执行结果
// "err"
// undefined
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

Promise.all()

Promise.race()

Promise.resolve()

Promise 实现

Promises/A+ 规范

Promises/A+ 规范文档

代码实现参考

重难点问题

then 方法链式调用后为什么要返回新的 promise 实例?

如我们理解,为保证then函数链式调用,then需要返回promise实例。但为什么返回新的promise,而不直接返回this当前对象呢?看下面示例代码:

const promise2 = promise1.then(function (value) {
  return Promise.reject(3);
})
1
2
3

假如then函数执行返回this调用对象本身,那么promise2 === promise1promise2状态也应该等于promise1同为resolved。而onResolved回调中返回状态为rejected对象。考虑到Promise状态一旦resolvedrejected就不能再迁移,所以这里promise2也没办法转为回调函数返回的rejected状态,产生矛盾。

通过 then 注册的回调函数为什么要异步执行?

var a = 1;

promise1.then(function (value) {
  a = 2;
})

console.log(a)
1
2
3
4
5
6
7

promise1内部执行同步或异步操作未知。

假如未规定then注册回调为异步执行,则这里打印a可能存在两种值。promise1内部同步执行操作时a === 2,相反执行异步操作时a === 1。为屏蔽依赖外部的不确定性,规范指定onFulfilledonRejected方法异步执行。

简单实现源码

// 用于异步执行通过 promise.then() 方法添加的回调函数
const asyncFn = (function() {
  if (
    typeof process === 'object' &&
        process !== null &&
        typeof process.nextTick === 'function'
  ) {
    // microtask
    return process.nextTick;
  } else if (typeof setImmediate === 'function') {
    // macrotask
    return setImmediate;
  }
  // macrotask
  return setTimeout;
})();

function Handler(onResolved, onRejected, promise) {
  this.onResolved = typeof onResolved === 'function' ? onResolved : null;
  this.onRejected = typeof onRejected === 'function' ? onRejected : null;
  this.promise = promise;
}

function Promise(fn) {
  // 省略非 new 实例化方式处理
  // 省略 fn 非函数异常处理

  // promise 状态变量
  // 0 - pending
  // 1 - resolved
  // 2 - rejected
  this._state = 0;

  // promise 执行结果
  this._value = null;

  // then(..) 注册回调处理数组
  this._deferreds = [];

  // 立即执行 fn 函数
  try {
    fn(
      value => {
        resolve(this, value);
      },
      reason => {
        reject(this, reason);
      }
    );
  } catch (err) {
    reject(this, err);
  }
}

Promise.prototype.then = function(onResolved, onRejected) {
  const res = new Promise(function() {});

  // 使用 onResolved,onRejected 实例化处理对象 Handler
  const deferred = new Handler(onResolved, onRejected, res);

  if (this._state === 0) {
    // 当前状态为 pendding,存储延迟处理对象
    this._deferreds.push(deferred);
  } else {
    // 当前 promise 状态不为 pending,调用 handleResolved 执行 onResolved 或 onRejected 回调
    handleResolved(this, deferred);
  }

  // 返回新 promise 对象,维持链式调用
  return res;
};

function resolve(promise, value) {
  // 非 pending 状态不可变
  if (promise._state !== 0) {
    return;
  }

  // Promise A+ 规范 2.3.1
  // value 和 promise 指向同一对象
  if (value === promise) {
    return reject(
      promise,
      new TypeError('A promise cannot be resolved with itself.')
    );
  }

  // Promise A+ 规范 2.3.2
  // value 和 promise 为 相同实现的 Promise,则使 promise 接受 value 的状态
  if (value && value instanceof Promise && value.then === promise.then) {
    const oldDeferreds = promise._deferreds;

    if (value._state === 0) {
      // Promise A+ 规范 2.3.2.1
      // value 为 pending 状态
      // 将 promise._deferreds 传递 value._deferreds
      // 偷个懒,使用 ES6 展开运算符
      value._deferreds.push(...oldDeferreds);
    } else if (oldDeferreds.length) {
      // Promise A+ 规范 2.3.2.2、2.3.2.3
      // value 为 非 pending 状态
      // 使用 value 作为当前 promise,执行 then 注册回调处理
      oldDeferreds.forEach(deferred => {
        handleResolved(value, deferred);
      });

      // 清空 then 注册回调处理数组
      value._deferreds = [];
    }
    return;
  }

  // Promise A+ 规范 2.3.3
  // value 是对象或函数
  if (value && (typeof value === 'object' || typeof value === 'function')) {
    let then;
    try {
      // Promise A+ 规范 2.3.3.1
      then = value.then;
    } catch (err) {
      // Promise A+ 规范 2.3.3.2
      reject(err);
    }

    // Promise A+ 规范 2.3.3.3
    // 如果 then 是函数,将 value 作为函数的作用域 this 调用之
    if (typeof then === 'function') {
      try {
        // 执行 then 函数
        then.call(
          value,
          function(value) {
            resolve(promise, value);
          },
          function(reason) {
            reject(promise, reason);
          }
        );
      } catch (err) {
        reject(promise, err);
      }
      return;
    }
  }

  // Promise A+ 规范 2.3.3.4、2.3.4
  // 改变 promise 内部状态为 `resolved`
  promise._state = 1;
  promise._value = value;

  // promise 存在 then 注册回调函数
  promise._deferreds.forEach(function(deferred) {
    handleResolved(promise, deferred);
  });
  promise._deferreds = [];
}

function reject(promise, reason) {
  // 非 pending 状态不可变
  if (promise._state !== 0) {
    return;
  }

  // 改变 promise 内部状态为 `rejected`
  promise._state = 2;
  promise._value = reason;

  promise._deferreds.forEach(deferred => {
    handleResolved(promise, deferred);
  });
  promise._deferreds = [];
}

function handleResolved(promise, deferred) {
  // 异步执行注册回调
  asyncFn(function() {
    const cb =
            promise._state === 1 ? deferred.onResolved : deferred.onRejected;

    // 传递注册回调函数为空情况
    if (cb === null) {
      if (promise._state === 1) {
        resolve(deferred.promise, promise._value);
      } else {
        reject(deferred.promise, promise._value);
      }
      return;
    }

    // 执行注册回调操作
    let res;
    try {
      res = cb(promise._value);
    } catch (err) {
      reject(deferred.promise, err);
    }

    // 处理链式 then(..) 注册处理函数调用
    resolve(deferred.promise, res);
  });
}
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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201