[js]不扎实的知识复习-promise

188 阅读6分钟

先使用已经造好的promise

听过两个不同的讲promise的视频,感觉先使用promise再学习如何创造会理解更好。mdn上也是这个顺序。而且实际开发中,我们可能也是用别人造好的promise更多。

以fetch做例子,fetch函数会返回一个promise,所以我们可以:

let promise = fetch('https://catfact.ninja/fact')

// 这里理论上可以写 (white promise is not resolved){wait.......} 但这样会造成阻塞

// 处理promise成功和失败的情况
promise.then(resp => console.log(res)) // then的return对象是一个新promise,如果then未处理,promise里的信息会被传递给catch
       .catch(err => console.log(err))

根据官方文档,then可以有以上两种穿参形式,我们可以只传递成功的cb,出错时的cb交给catch,也可以把两个cb都传给then image.png 也就是说,下面这两种写法是等价的。

  promise.then(resp => console.log(resp))
         .catch(err => console.log(err))
         
  promise.then(resp => console.log(resp), err => console.log(err))

下面来看一下输出结果。 上面api获取的数据其实应该是下面这样,

image.png 但实际打印出来是这样

image.png

为了拿到我们想要的数据,可以使用json()方法,但是json方法返回的还是一个promise,所以我们需要再次用then来拿数据。

promise.then(resp => resp.json().then(data => console.log(data)))
       .catch(err => console.log(err))

上面的例子promise我们只使用了一次,可以省去变量命名,直接写:

fetch('https://catfact.ninja/fact').then(resp => {}, err=> {})

make your own promise

假设我们直接用new构建一个promise,然后调用then,看看会发生什么

delay().then(console.log('delay'))

function delay() {
    return new Promise();
}

这时浏览器会报错

image.png

也就是说没有promise resolver,而且这个promise resolver需要是函数。 可以理解为promise像是一个合约,合约达成了做什么? 违反合约做什么?这些都是需要提前规定好并写入合约,这样这份合约才能生效。

要创建promise,我们必须要定义一个用来处理promise状态改变后做什么的函数,官方文档称它为executor(执行器)

new Promise(executor)

而executor的样子是:

function(resolutionFunc, rejectionFunc){
      // 通常是一些异步操作
    }

可以用箭头函数直接简写为:

function delay() {
    return new Promise((resolve, reject) => { });
}

接着把代码改造成这样

delay().then(resp => sayHello())

  
function sayHello() {
    console.log('hello')
 }

function delay() {
    return new Promise((resolve, reject) => { resolve() });
}
// 这个resolve实际上是then成功后做的事,reject则是catch error后做的事

console会输出“hello”

promise并不是说拿到resolve和reject方案后,在fulfill时直接调用resolve,在发生错误时直接调用reject,而是可以让你再做一些别的事,比如在1秒后再调用resolve。下面补充了一些代码,加上了错误处理


delay('string').then(resp => sayHello())

function delay(time) {
    return new Promise((resolve, reject) => {
       if (isNaN(time)) {
          reject(new Error('time is not a valid number'))
        }  // 错误判定必须放在前面,不然resolve一定会执行。
       setTimeout(resolve(), time)
    });
}

代码执行情况

image.png

await async

这块我需要整理的思路比较少,把上面的fetch相关那部分改造一下。

let url = 'https://catfact.ninja/fact'
getCatFact()
   .then(result => console.log(result))
   .catch(err => console.log(err))

async function getCatFact() {
    let result = await fetch(url)
    let json = await result.json()
    return json
}

Promise.all() & Promise.allSettled

当我们有好几个promise需要处理时,直接把它们每个都书写出来,执行顺序未必是我们想要的。

想要保证执行顺序的一种方法是把他们chain起来。

       // 理论上下面这种写法,是不能保证执行顺序一定是书写顺序的
        getCatFact().then(result => {
            console.log('result 1');
        })

        getCatFact().then(result => {
            console.log('result 2');
        })

         getCatFact().then(result => {
            console.log('result 3');
        })

 

       // 把它们chain起来能保证顺序
        getCatFact()
            .then(result => {
                console.log(result)
                return getCatFact()
            })
            .then(result => {
                console.log(result)
                return getCatFact()
            })
            .then(result => {
                console.log(result)
            })

我们还可以用Promise.all()来解决问题。它的basic idea是:

let promises = [___, ____, ____];
Promise.all(promises)
   .then(result => {
        // do something  
        // result会是一个array
    })
   .catch(err => console.log(err))

前面的chain可以替换成下面:

let promises = [getCatFact(), getCatFact(), getCatFact()]
Promise.all(promises).
    then(result => console.log(result))

现在,假设我们故意制造一个error

      let url1 = 'https://catfact.ninja/fact'
      let url2 = 'https://catfact.ninjamiss/fact' // 这里url有错误
      let url3 = 'https://catfact.ninja/fact'

      let promises = [getCatFact(url1), getCatFact(url2), getCatFact(url3)]
      Promise.all(promises)
          .then(result => console.log(result))


      async function getCatFact(url) {
          let result = await fetch(url)
          let json = await result.json()
          return json
      }

这时控制台不会输出任何我们想要的cat fact,而是会直接报错。

这也反应出Promise.all的特性:all or nothing,要么全部输出,要么没有结果。

如果要避免这点,可以使用Promise.allSettled()

      Promise.allSettled(promises)
          .then(result => console.log(result))

此时它会以对象构成的数组形式输出数据

image.png

此时,如果我们想要拿到成功的数据,可以这样做:

      Promise.allSettled(promises)
       .then(result => {
           result.forEach(r => {
               if (r.status == "fulfilled") {
                   console.log(r.value)
               }
           })
       })

尚硅谷课程笔记

为什么要用promise

  1. 支持链式调用,可以解决回调地狱的问题。

image.png 2. callback比較靈活,不需要在一开始就指定好,而可以在then的时候再进行指定。

基本流程

image.png

一些注意事项

  1. executor是会立刻执行的。
  2. Promise.resolve()

当参数是非 Promise类型时,Promise.resolve() 结果以这个参数为结果,并且状态为fulfilled

        const p  = Promise.resolve('hello')
        p.then(r => console.log(r)) // hello

当参数是Promise类型时,返回的Promise状态和结果由参数中的Promise决定。

        const fullfilledP = new Promise((resolve, reject) => {
            resolve('success')
        })
        const p = Promise.resolve(fullfilledP)
        console.log(p) // [[PromiseState]]: "fulfilled"  , [[PromiseResult]]: "success"

        const rejectedP = new Promise((resolve, reject) => {
            reject('error')
        })
        const p2 = Promise.resolve(rejectedP)
        console.log(p2) // [[PromiseState]]: "rejected", [[PromiseResult]]: "error"
  1. Promise.reject() 当参数为Promise时,不论参数Promise状态如何,返回的Promise都是rejected状态,它的result也会是参数Promise对象。
        const fullfilledP = new Promise((resolve, reject) => {
            resolve('success')
        })
        const p = Promise.reject(fullfilledP)
        console.log(p) // [[PromiseState]]: "rejected" [[PromiseResult]]: Promise

        const rejectedP = new Promise((resolve, reject) => {
            reject('error')
        })
        const p2 = Promise.reject(rejectedP)
        console.log(p2) // [[PromiseState]]: "rejected" [[PromiseResult]]: Promise
       
  1. 改变promise状态和指定回调函数谁先谁后? 根据情况都有可能。 当在执行器中直接调用resolve()/ reject() 或者 延迟更长时间才调用then时,状态改变会先发生。

5.then方法的返回结果是由什么决定的? 由指定的callback的返回结果决定的。

        const promise = new Promise((resolve, reject) => {
            resolve()
        })

        // 无return值
        const thenReturnValue = promise.then(() => {})
        console.log(thenReturnValue) // [[PromiseState]]: "fulfilled" // [[PromiseResult]]: undefined


        // return 普通值
        const thenReturnValue2 = promise.then(() => {
            return 123
        })
        console.log(thenReturnValue2) //[[PromiseState]]: "fulfilled" [[PromiseResult]]: 123

        // return Promise
        const thenReturnValue3 = promise.then(() => {
            return Promise.reject('error')

        })
        console.log(thenReturnValue3) // [[PromiseState]]: "rejected" [[PromiseResult]]: "error"


        // throw exception
        const thenReturnValue4 = promise.then(() => {
            throw 'error'

        })
        console.log(thenReturnValue4) // [[PromiseState]]: "rejected" [[PromiseResult]]: "error"



        // 当promise被reject,但是handler返回一个成功promise时的测试
        const p = new Promise((r1, r2) => {
            r2('error')
        })
        console.log(p.then(() => {}, () => { //[[PromiseState]]: "fulfilled" [[PromiseResult]]: "success"

            return Promise.resolve('success')
        }))
  1. 链式then调用的结果是由前面的返回值决定的 例如:
let p = new Promise((resolve, reject) => {
    resolve(123)
})

 
  p.then(res => {
     return new Promise((resolve, reject) => {
        resolve('success')
     })
  }).then(res => {
       return 456
  }).then(res => console.log(res)) //456
  1. 异常穿透问题 在链式调用时任何环节的错误只需在最后用catch捕获。
let p = new Promise((resolve, reject) => {
    resolve('success')
})

p.then((res) => {
     return new Promise((resolve, reject) => {
         reject('error')
     })
}).then(res => {
    return Promise.resolve(0)
}).catch(err => console.log(err)) //error
  1. 中断promise链 例如假设我们想在console.log(1)之后就中断,该如何做?
let p = new Promise((resolve, reject) => {
   resolve('hello')
})

p.then(res => {
   console.log(1) // 执行这里后中断
}).then(res => {
   console.log(2)
}).then(res => {
    console.log(3)
})

假设我们按照以前中断一个函数的方法直接return,试一试,先看一下直接return后then的返回结果是什么。

let result = p.then(res => { 
    console.log(1)
    return
})

console.log(result) // [[PromiseState]]: "fulfilled" , [[PromiseResult]]: undefined

也就是说直接return后,then会返回一个状态是resolved、结果是undefined的promise,因为结果是resolved,那么必然之后的then还会执行。

同样,return null、false等也是一样的结果。

假设这里直接抛出异常,确实可以起到中断效果,但也会报错。

这里最好的处理方法只有一个,就是return一个pengding状态的promise,因为then函数只有在promise状态改变由pending发生改变时,才会被调用。

p.then(res => {
     console.log(1)
     return new Promise(() => {})
}).then(res => {
     console.log(2)
}).then(res => {
     console.log(3)
})