Promise对象
用于实现JS的异步机制, 非常重要!!!
执行流程(无其他异步操作, 如定时器):
用一段代码理解一下:
var p = new Promise(function(resolve, reject){
console.log(1)
resolve(2)
resolve(3)
reject(4)
console.log(5)
})
console.log(6)
p.then(function(val){
console.log('then',val)
}).catch(function(val){
console.log('catch', val)
})
console.log(7)
- 创建一个Promise对象的时候, 立即执行传入的function.
- 输出1; 遇到resolve, pending状态改为fulfilled(已完成). 并保留参数2(后面then要用)
- 之后遇到resolve和reject, 由于状态已经不是pending了, 直接忽略这两个函数.
- 打印5. 整个函数执行完成, 返回一个Promise对象(的引用)给变量p.
- 打印6. 遇到then/catch, 现在的状态是fulfilled, 所以执行then.(如果Promise状态为rejected, 执行catch的回调函数). 但不是立即执行, 而是把传入的function加到事件队列尾部(你需要先了解JS的事件队列).
- 打印7. 主程序运行完毕, 然后是下一个事件, 就是then里面的这个函数执行. 函数接受一个参数, 就是上面resolve传入的参数2. 所以这里打印
then 2
. - 顺序输出为: 1, 5, 6, 7, then 2
除了 then(onresolve).catch(onreject)
的写法, 也可以不写catch, 把两个函数都作为then的参数, 即 then(onresolve, onreject)
的写法, 不过前者更直观一些, 推荐.
带有定时器的Promise
前面的是一个非常简单的例子. 如果我们在Promise传入的函数中设置了一个定时器, 延迟一定时间后在改变Promise的状态, 这样的话下面的then/catch都只能读取到pending状态? 这样会发生什么情况呢? 如下代码:
var p = new Promise(function(resolve, reject){
console.log(1)
//把修改状态的语句写入定时器.
setTimeout(function(){
resolve(2)
},3000)
console.log(5)
})
console.log(6)
p.then(function(val){
console.log('then',val)
}).catch(function(val){
console.log('catch', val)
})
console.log(7)
直接说结论, 输出的结果同上. 不过最后一个结果会等待三秒才输出.
当我们调用then/catch的时候, 如果当前的状态为pending, 则将对应的函数压入一个队列, 当改变Promise状态时, 再逐个执行这些函数.
下面来两段一个大佬自己实现的模拟Promise源码, 很觉得很有助于理解Promise的实现原理和运行机制.
原博客链接: https://www.jianshu.com/p/43de678e918a
思想还是很简单的:
-
当我们在创建Promise传入的回调函数中, 如果遇到resolve/reject, 判断当前状态是否为pending, 不是pending则直接忽略.
-
如果是pending, 则修改当前状态为fulfilled/rejected.
-
看第二段代码, 当我们执行then函数的时候, 如果Promise的状态为pending, 把对应的执行函数压到队列里面. 如果为fulfilled/rejected, 则直接执行then参数里面的函数.
-
再回到第一段代码, 就是上面then执行时遇到pending的情况, 当pending转化为fulfilled/rejected的时候, 调用相应方法, 查询执行函数队列, 并按顺序执行队列里面的函数. 这样就比较完美的实现了异步机制.
-
对于下面的第二段代码, 有个地方没实现好, then方法应该是异步执行的, 这里当Promise已经是fulfilled时, 直接执行then传入的回调函数. 我觉得应该这样去实现:
setTimeout(()=>{onFulfilled(_value)},0)
.
// 添加resovle时执行的函数
_resolve (val) {
if (this._status !== PENDING) return
// 依次执行成功队列中的函数,并清空队列
const run = () => {
this._status = FULFILLED
this._value = val
let cb;
while (cb = this._fulfilledQueues.shift()) {
cb(val)
}
}
// 为了支持同步的Promise,这里采用异步调用
setTimeout(() => run(), 0)
}
// 添加reject时执行的函数
_reject (err) {
if (this._status !== PENDING) return
// 依次执行失败队列中的函数,并清空队列
const run = () => {
this._status = REJECTED
this._value = err
let cb;
while (cb = this._rejectedQueues.shift()) {
cb(err)
}
}
// 为了支持同步的Promise,这里采用异步调用
setTimeout(run, 0)
}
then的实现:
// 添加then方法
then (onFulfilled, onRejected) {
const { _value, _status } = this
switch (_status) {
// 当状态为pending时,将then方法回调函数加入执行队列等待执行
case PENDING:
this._fulfilledQueues.push(onFulfilled)
this._rejectedQueues.push(onRejected)
break
// 当状态已经改变时,立即执行对应的回调函数
case FULFILLED:
onFulfilled(_value)
break
case REJECTED:
onRejected(_value)
break
}
// 返回一个新的Promise对象
return new MyPromise((onFulfilledNext, onRejectedNext) => {
})
}
但是then地用法不止如此, 有时候你可能会看到一连串的then. 在上面代码也看到, then返回的是另一个Promise对象, 所以then之后可以继续接then的.返回的Promise对象的状态和参数是怎么样的呢? 源码我就不继续举了, 来个例子了解一下就好了.
var p = new Promise(function(resolve, reject){
resolve(666)
})
.then(function(val){
console.log('then',val)
return 'hello'
}).then(function(val){
console.log(val)
return 'world'
}).then(function(val){
console.log(val)
return new Promise(function(resolve, reject){
setTimeout(function(){
reject('goodbye')
},3000)
})
}).catch(function(val){
console.log(val)
return 'see you again'
}).then(function(val){
console.log(val)
})
console.log(999)
代码略长, 但是不难理解.
- 创建一个Promise对象, 调用第一个then方法, 这个是和上面讲的列子是一样的. 不同的是, 第一个then返回一个
hello
字符串. - 调用第二个then, 我们可以看到它的回调函数传入的参数就是上一个then返回的内容. 说明调用then方法的时候Promise内部会帮我们创建一个新的Promise对象, 而且是调用了resolve并传入then返回的值(hello).
- 然后到第三个then了, 也是输出上一个then的返回值. 但是这个then返回的是一个Promise对象. 直接返回Promise对象的话这个就直接把这个promise对象最为then的返回值, 相当于之后的then/catch都是这个Promise对象的调用.
- 因为上一个then返回的是一个rejected状态的Promise对象, 所以找catch来调用. 在catch里面也是直接返回一个字符串.
- 下一个调用到的是then而不是catch, 说明即使在catch里面返回的非promise数据, 系统默认新建的还是fulfilled状态的Promise对象(就是上面说的默认执行了resolve).
这一连串的then并不会阻塞主函数的运行, 所以输出的结果为: 999, then 666, hello, world, goodbye, see you again
.
async 和 await
定义函数时可以在函数前添加asnyc关键字, 函数执行与普通函数一致, 只不过对返回值进行了Promise的封装. 返回的Promise状态为fullfilled. (类似下面的封装).
function bar(fn, self){
return function(...rest){
let result = fn.apply(self, rest)
return Promise.resolve(result)
}
}
async的做法非常好理解. 但是await做的事情就多一些了.
首先, await必须在async的函数内部使用. await可以看作是一个运算符, 右边必须是一个Promise对象, 由于async返回的一定是一个promise对象, 所以await后面常跟async的函数(有点套娃的感觉, 但是只要理解上面说的await的用法, 这个说法就很容易接受).
await会阻塞当前函数的执行, 直到await拿到结果, 才会继续执行后面的内容. 那await等的是什么呢? 等的是对应的Promise对象, 等到他状态变为resolved.
console.log(1)
async function foo(){
console.log(2)
let tmp = await new Promise(resolve=>{
setTimeout(function(){
resolve(3)
},2000)
})
console.log(tmp,4)
console.log(5)
return 6
}
console.log(foo())
console.log(7)
//输出结果为: 1 2 Promise<pending> 7 (3,4) 5
从结果可以看出, await部分之前与普通函数一样正常执行. 但是之后的部分直接作为一个Promise对象封装并返回. 另外, await运算后不是一个Promise对象, 而是resolve的传入. 上面的函数和这个这个写法是等价的.
async function foo(){
return new Promise(resolve=>{
setTimeout(()=>{
resolve(3)
},2000)
}).then(v=>{
console.log(v,4)
console.log(5)
return 6
})
}