搞懂Promise, async,await(附代码)

Promise对象

用于实现JS的异步机制, 非常重要!!!

执行流程(无其他异步操作, 如定时器):

new Promise
执行传入的函数
遇到resolve
promise的状态由pending改为fulfilled
遇到reject
promise的状态由pending改为rejected
继续执行函数之后的语句,如再遇到reject/resolve则忽视
返回一个promise对象
调用then/catch
调用then/catch传入的函数,对应参数为上面resolve/reject函数传入的参数

用一段代码理解一下:

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
  })
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值