基本概念
抽象上看,Promise是JavaScript中进行异步编程的解决方案。
具体来看,Promise是一个对象,它通常用于描述 现在开始执行,一段时间后才能获得结果的异步行为,内部保存了该异步行为的结果。
Promise对象有且仅有以下3种状态:
- pending:待定(进行中)
- fulfilled:成功
- rejected:失败
一个Promise的状态转换仅有以下2种,Promise一旦决议,就会一直保持其决议结果(fulfilled或rejected)不变:
- pending 到 fulfilled
- pending 到 rejected
基础用法
Promise对象构造器接收一个executor执行器
let p = new Promise((resolve,reject) => {
//这个代码块是executor
})
执行器通常承担2个任务:
- 初始化一个异步行为
- 控制状态的最终转换
执行器接收两个函数作为参数,其中:
- resolve:用于将状态 pending 转换成 fulfilled
- reject:用于将状态 pending 转换成 rejected
let p = new Promise((resolve,reject) => {
setTimeout(() => {
resolve()
},1000)
})
在setTimeout真正被执行的1000ms后,对象p的状态从pending转换成fulfilled,并将resolve对应的回调函数放入异步队列(等待执行)。
实例方法.then( )
then方法可接收2个函数作为参数
- 第一个为onResolved:当executor中执行resolve( )的时候,就会进入onResolved这个函数,传递成功的value
- 第二个为onRejected:当executor中执行reject( )的时候,就会进入onRejected这个函数,传递失败的reason
我们可以把这2个函数单独写在外部
let p = new Promise((resolve, reject) => {
console.log('promise执行成功')
resolve(3)
})
p.then(onResolved,onRejected)
function onResolved(value){
console.log('resolved:'+ value)
}
function onRejected(reason){
console.log('rejected:'+ reason)
}
//输出如下:
//promise执行成功
//resolved:3
也可以将函数直接写在then括号内(省略函数名的定义)
let p = new Promise((resolve, reject) => {
console.log('promise执行成功')
resolve(3)
})
p.then((value)=>{
console.log('rejected:'+ value)
}, (reason)=>{
console.log('rejected:'+ reason)
})
//与上面的写法完全等价
then方法的参数是可选的
当参数只有onResolved的时候,可以这样写:
let p = new Promise((resolve, reject) => {
resolve(3)
})
p.then((value)=>{
console.log('resolved:'+ value)
})
当参数只有onRejected的时候,需要把第一个参数设置为null
let p = new Promise((resolve, reject) => {
reject('error')
})
p.then(null,(reason)=>{
console.log('rejected:'+reason)
})
实例方法 .catch( )
catch专门用于处理失败的promise对象,它只接收一个 onRejected函数作为参数
let p = new Promise((resolve, reject) => {
reject('error')
})
p.catch((reason) => {
console.log(reason)
})
如何判断Promise或者类似于Promise的值
虽然Promise是通过new Promise(...)语法创建,但如果用p instanceof Promise来检查某个值是否为promise,是不全面的,因为:
- Promise的值可能是从其他浏览器窗口(iframe)中接收到的,这个窗口的Promise可能和当前窗口的不一样,所以无法识别Promise实例
- 某一些库或框架会实现自己的Promise,而不是使用原生的ES6 Promise
所以,识别Promise就是定义一个thenable的东西,任何具有then(...)方法的对象和函数,都被称为Promise一致的thenable。
const thenable = {
then(res) {
setTimeout(res, 3000)
}
}
// 1
Promise.resolve()
.then(()=>thenable)
.then(()=>console.log('3秒过去'));
// 2
!async function() {
const sleep = () => thenable
await sleep();
console.log('3秒过去');
}();
如上面一段代码,无论是哪一种写法,都会经过3秒然后打印。证明判断一个对象是不是Promise或行为方式类似于Promise,仅仅判断它是否有 then 函数即可。
if (
p !== null &&
(
typeof p === "object" ||
typeof p === "function"
) &&
typeof p.then === "function"
) {
// 这是一个thenable对象
} else {
// 这不是一个thenable对象
}
如果我们无意给某个对象加上then函数,却不希望它被当作Promise或者thenable,那恐怕会事与愿违,他会被自动识别为thenable,按照特定的规则处理。所以这可能是有害的,可能导致难以追踪的bug。
这种类型检测叫鸭子类型(duck typing): When I see a bird that walks like a duck and swims like a duck and quacks like a duck, I call that bird a duck.
为什么要使用Promise
如果用普通的回调来提供异步方案,会有一些信任的问题,如下面这段代码:
// A
ajax('..', function(..) {
// C
});
// B
A和B发生于现在,C可能会延迟到将来发生,并且是在第三方的控制下。这种控制反转会出现五个问题:
- 回调调用次数太少或太多(第三方可能会不如我们所期待地多次调用回调函数)
- 调用回调过早(在追踪之前)
- 调用回调过晚(甚至没有调用)
- 没有把所需要的环境/参数传给回调函数
- 吞掉可能出现的错误或异常
Promise的特性就是用来解决这些问题的:
-
解决回调调用过早或过晚:即使是立即完成的promise,其回调then函数里的内容也总是会被放到微任务队列里,异步执行,即使有多个回调函数,它们的执行是独立的,不会受到影响。比如:
p.then(function() { p.then(function() { console.log('C'); }); console.log('A'); }) p.then(function() { console.log('B'); }) // A B C -
解决回调次数过少(未调用):我们可以设定一个超时函数,并且用promise.race来解决超时未调用的问题
function timeout(delay) { return new Promise((resolve, reject) => { setTimeout(() => { console.log('Timeout!'); }); }); } Promise.race([p, timeout(3000)]).then(resCb, rejCb); // 当超时或者p抛出错误,都会调用rejCb -
解决回调次数过多:若代码试图多次调用resolve或reject,Promise只会接受第一次决议,并忽略任何后续的调用。
var p = new Promise((resolve, reject) => { // ... resolve('1'); // 之后的决议全部忽略 resolve('2'); reject('3'); }); p.then((value) => { console.log(value); }, (reason) => { console.error(reason); }); // 1 -
解决未能传递参数/环境值:Promise的resolve和reject都只能传一个参数,第二个参数及之后的都会被忽略,如果未显式定义,则这个值为undefined,详情见后文的链式调用流。
-
解决吞掉异常或错误的问题:每个then函数都会返回另一个promise,所以任何地方抛出错误,都会导致相应的promise被拒绝,可以在catch或者then的第二个参数中定义异常处理,详情见后文的错误处理。
参考文章:
《你不知道的JavaScript》(中卷)