Promise使用

87 阅读4分钟

1、Promise遵循规则

  • Promise 是一个提供符合标准的 then () 方法的对象。
  • 初始状态是 pending,能够转换成 fulfilled 或 rejected 状态。
  • 一旦 fulfilled 或 rejected 状态确定,再也不能转换成其他状态。
  • 一旦状态确定,必须要返回一个值,并且这个值是不可修改的。

img

2、解决问题

由于 JavaScript 是单线程事件驱动的编程语言,通过回调函数管理多个任务。开发中,因为回调函数的滥用,很容易产生被人所诟病的回调地狱问题。Promise 的异步编程解决方案比回调函数更加合理,可读性更强。

function callback() {
    return function() {
      request(url1, function() {
        request(url2, function() {
          request(url3, function() {
            request(url4, function() {
              request(url5, function() {
                request(url6, function() {
                })
              })
            })
          })
        })
      })
    }
  }

现实业务场景:

function test1() {
    getCode({
      userId,
      success: code => {
        getList({
          code,
          success: data => {
            renderList({
              list: data.list,
              success: () => {
                uploadLog()
              },
              fail: err => {
                console.error(err)
              }
            })
          },
          fail: err => {
            console.error(err)
          }
        })
      },
      fail: err => {
        console.error(err)
      }
    })
  }

使用Promise后:

function test2() {
    getCode({
      userId,
    }).then(() => {
      return getList()
    }).then(() => {
      return renderList()
    }).then(() => {
      return uploadLog()
    }).catch((err) => {
      console.error(err)
    })
  }

3、实现Promse

Promise 的运转实际上是一个观察者模式,then() 中的匿名函数充当观察者,Promise 实例充当被观察者。

img

// 实现
  function promise() {
    let pending = [] // 收集观察者
    let value = undefined
    return {
      resolve: (_value) => {
        value = _value
        if (pending) {
          pending.forEach(callback => callback(value))
          pending = undefined
        }
      },
      then: (callback) => {
        if (pending) {
          pending.push(callback)
        } else {
          callback(value)
        }
      }
    }
  }

  // 测试
  function testPromise() {
    let p = promise()
    setTimeout(() => {
      p.resolve('resolve!')
    }, 5000)
    return p
  }

  testPromise().then(res => {
    console.log(res)
  })

  console.log('endend')

Promise 出现后使用 then() 接收事件的状态,且只会接收一次。

const p = new Promise(r => r(1))
    .then(res => {
      console.log(res) // 1
      return Promise.resolve(2)
        .then(res => res + 10) // === new Promise(r => r(1))
        .then(res => res + 10) // 
    })
    .then(res => {
      console.log(res) // 22
      return 3 // === Promise.resolve(3)
    })
    .then(res => {
      console.log(res) // 3
    })
    .then(res => {
      console.log(res) // undefined
      return 'meme'
    })

  p.then(console.log.bind(null, '输出最后end'))

由于返回一个 Promise 结构体永远返回的是链式调用的最后一个 then(),所以在处理封装好的 Promise 接口时没必要在外面再包一层 Promise。

 // 包一层 Promise
  function api1() {
    return new Promise((resolve, reject) => {
      axios.get(url).then(data => {
      // ...
        resolve(data)
      })
    })
  }
 // better
  function api2() {
    return axios.get(url).then(data => {
    // ...
      return data
    })
  }

4、管理多个 Promise

Promise.all() / Promise.race() 可以将多个 Promise 实例包装成一个 Promise 实例,在处理并行的、没有依赖关系的请求时,能够节约大量的时间。

 function wait(ms) {
   return new Promise(resolve => setTimeout(resolve.bind(null, ms), ms))
 }


 Promise.all([wait(2000), wait(4000), wait(3000)])
   .then(console.log)



 Promise.race([wait(2000), wait(4000), wait(3000)])
   .then(console.log)

5、async&await 用法

async&await 实际上只是建立在 Promise 之上的语法糖,让异步代码看上去更像同步代码,所以 async&await 在 JavaScript 线程中是非阻塞的,但在当前函数作用域内具备阻塞性质。

 let ok = null
 async function test() {
   console.log(1111)
   console.log(await new Promise(resolve => ok = resolve))
   console.log(3333)
 }
 test() 
 ok(2222) 

使用 async&await 的优势

写更少的代码,不需要特地创建一个匿名函数,放入 then() 方法中等待一个响应。

 function getInfo() {
   return getData().then(
     data => {
       return data
     }
   )
 }
 
 async function getInfo() {
   return await getData()
 }

处理条件语句

当一个异步返回值是另一段逻辑的判断条件。使用 async&await 将使代码可读性变得更好。

// Promise
 function getInfo() {
   getValue().then(
    value => {
       if (value === 22) {
         return getInfo1().then(
           data => {
             // ...
           }
         )
       } else {
         return getInfo2().then(
           data => {
             // ...
           }
         )
       }
     }
   )
 }

 // async await
 async function getInfo() {
   const value = await getValue()
   if (value === 22) {
     const data = await getInfo1()
     // ...
   } else {
     // ...
   }
 }

处理中间值

异步函数常常存在一些异步返回值,很容易成为另一种形式的 “回调地狱”。

 // Promise
 function getInfo() {
   getCode().then(
     data => {
       getLevel(data).then(
         data => {
           getInfo(data).then(
             data => {
               // todo
             }
           )
         }
       )
     }
   )
 }

 // async / await
 async function getInfo() {
   const code = await getCode()
   const level = await getLevel(code)
   const data = await getInfo(level)
   // todo
 }

对于多个异步返回中间值,搭配 Promise.all 使用能够提升逻辑性和性能。

// async / await & Promise.all
 async function test() {
   // ...
   const [a, b, c] = await Promise.all([Fn1(), Fn2(), Fn3()])
   const d = await Fn4()
   // ...
 }

靠谱的 await

await'str' 等于 await Promise.resolve('str'),await 会把任何不是 Promise 的值包装成 Promise

避免滥用 async&await

await 阻塞 async 函数中的代码执行,在上下文关联性不强的代码中略显累赘。

// async / await
 async function init() {
   render1(await get1()) 
   render2(await get2())
 }

 // Promise
 function init() {
   get1()
     .then(render1)
     .catch(console.error)
   get2() 
     .then(render2)
     .catch(console.error)
 }

错误处理

1、链式调用中尽量结尾跟 catch 捕获错误,而不是第二个匿名函数。因为规范里注明了若 then() 方法里面的参数不是函数则什么都不做,所以 catch(rejectionFn) 其实就是 then(null, rejectionFn) 的别名。

asyncFn().then(
   successFn, // 产生的错误无法捕获
   errorFn // `errorFn` 捕获 `asyncFn`
 )

asyncFn () 抛出来的错误 errorFn 会正常接住,但是 successFn 抛出来的错误将无法捕获,所以更好的做法使用 catch。

asyncFn()
   .then(successFn)
   .catch(errorFn) // 尽量使用 `catch`

也可以通过 errorFn 来捕获 asyncFn() 的错误,catch 捕获 successFn 的错误。

作者: 咸鱼翻身