在工作中遇到Promise的很多坑,一直想着对这些知识点进行总结。
javascript的异步处理,大多会想到通过回调函数来解决,但回调带来的问题也比较明显:
- 在多层嵌套的回调函数中,无法判断什么时候完成异步,需要在外层作用域声明一些变量,这样容易被其他函数或变量修改。
- 因为异步函数在新的栈中运行,无法获取到之前栈的信息,之前的栈也无法捕获新的栈中抛出的错误,无法用try-catch处理错误。
Promise可以将异步处理模块化,规范化。使得代码更简洁,优雅,可读性更好。
基础用法:
1 | var promise = new Promise((resolve, reject)=>{ |
Promise 构造函数内有个执行器,将要进行的异步操作放入执行器中,Promise 实例一旦创建,执行器立即执行,执行器执行完毕后改变实例的状态,接着去执行相应的函数。Promise 的状态只能由执行器改变。
Promise 实例的特点:
- 对象状态不受外部影响,只有内部执行器可以改变状态。
- 状态一旦改变,就不会再变,任何时候都可以得到这个结果。
Promise的链式调用
Promise支持链式调用,因为调用.then方法每次都会返回新的 Promise 对象。
状态响应函数(即 then 方法)的返回值可以是以下三种
- 一个 Promise 对象
- 一个同步的值或者是 undefined
- 同步的 throw 一个错误
- 如果返回的是一个 Promise 对象(即异步操作),那后一个回调函数(.then()方法),会等待该 Promise 对象状态发送变化,才会去执行。
- 如果返回一个同步的值,会默认“立刻”执行下一个.then(),如果不返回值,会传入一个 undefined,但不影响接下来的执行。同时将同步的值转化为 Promise 风格的代码。
- 如果.then 方法里
throw new Error()
, 会被 catch 捕获到。
Promise.resolve()
会返回一个 fulfilled 的 Promise 实例或原始的 Promise 实例。
Promise.resolve()
:当参数为空,返回一个状态为 fulfilled 的 Promise 实例。Promise.resolve(object)
:当参数是 Promise 无关的值,同上,不过 fulfilled 响应函数会得到这个值。Promise.resolve(promise)
:当参数是 Promise 实例,则返回该实例,不做任何修改。Promise.resolve(thenable)
:当参数是.thenable,立刻执行 then 函数。
Promise.reject()
返回一个使用接收到的值进行了 reject 的新的 Promise 对象。
和 Promise.resolve()
的区别是:当参数为 Promise 对象时,返回一个全新的 Promise 对象。
Promise使用常见错误
注意一:.then 方法每次返回一个新的 Promise 对象。区分链式调用和非链式调用
1 | // 1: 对同一个 Promise 对象同时调用 `then` 方法 |
下面是一个由 then 方法导致的比较容易出错的例子
1 | function anAsyncCall() { |
这种错误也是 Promise 的反模式,更多反模式参考这篇文章。
这样会存在很多问题: 首先在 somethingComplicated 方法中产生的异常不会被外部捕获,此外,也不能得到 then 的返回值。当最后返回的是第一个 Promise 而不是 Promise 调用 then 方法后的结果,Promise 链也随机断掉。
正确的做法应该是:
1 | function anAsyncCall() { |
1 | function anAsyncCall() { |
注意二:.then 方法执行完必须将结果 return 出来。
.then 方法支持链式调用,不 return 的话,then 里的方法就会返回 undefined。所以要养成在 then 方法内部永远显式的调用 return 或throw 的习惯。
1 | function test(){ |
注意三:catch 和 then 的区别
catch 是 then 的语法糖,它是then(null, rejection)
的别名,也就是当 Promise 对象状态变成 rejected 时会执行 catch,但是catch 调用完之后还是会返回一个 Promise 实例。
如果用 then,第一个回调抛出来的错误,第二个回调函数(then)不会捕获1
2
3
4
5somePromise().then(function () {
throw new Error('error');
}).catch(function (err) {
// I caught your error! :)
});
1 | somePromise().then(function () { |
即:当使用 then(resolveHandler, rejectHandler)
,rejectHandler 不会捕获在 resolveHandler 中抛出的错误。
所以最好也不用 then 的第二个回调,转而用 catch 方法。
问题: .catch() 后面如果跟着 then() 会怎么处理?
.catch() 会返回 Promise 实例,如果不抛出错误,后面跟着 .then() 会执行,如果在 catch 中抛出错误,后面的 .then() 会跳过去,直接走向最后的 .catch()。
注意四:永远传递一个函数到then方法里
看下面代码打印出什么?1
2
3
4
5Promise.resolve('foo')
.then(Promise.resolve('bar'))
.then(function (result) {
console.log(result);
});
结果为:foo
当then方法接收一个非函数参数时,会解释为then(null)
,导致之前的 Promise 的结果丢失,发生 Promise 穿透。
mdn上解释:
如果忽略针对某个状态的回调函数参数,或者提供非函数 (nonfunction) 参数,那么 then 方法将会丢失关于该状态的回调函数信息,但是并不会产生错误。如果调用 then 的 Promise 的状态(fulfill 或 rejection)发生改变,但是 then 中并没有关于这种状态的回调函数,那么 then 将创建一个没有经过回调函数处理的新 Promise 对象,这个新 Promise 只是简单地接受调用这个 then 的原 Promise 的终态作为它的终态。
所以,如果
1 | Promise.resolve('foo') |
最后
最佳实践:
- 回调函数中一定要使用 return 语句,避免丢失状态和结果。
- 在最后一定调用 catch 方法,用来捕获整体的异常。
- 永远传递函数给 then 方法。
参考文章: