Promise及相关知识细学

学习关键语句:
Promise
promise学习
promise.all
promise.race
promise.resolve

1. 写在前面

promise 是前端绕不开的东西 , 所以我们一定要好好学习 , 写这篇文章的目的是加深对 promise 的学习和使用程度

2. 开始

2.1 准备

首先创建一个文件夹 , 里面新建一个 index.html 和 index.js
我们在浏览器环境观看 promise 的使用效果
Promise及相关知识细学_第1张图片

2.2 看

我们要先看一看这个 promise 到底是啥

let o1 = new Object()
console.log(o1)

let p1 = new Promise((resolve, reject) => { })
console.log(p1)

我们可以看到 promise 比正常的对象要多了两个东西 , 一个是 PromiseState , 值为 pending ; 一个是 PromiseResult , 值为 undefined

Promise及相关知识细学_第2张图片

我们从实例化方式和打印对象中可以看出 , promise 是一个构造函数或者说类 , 看上去只不过是在实例化时传入一个函数而已 , 那么我们继续看一看一个简单的 promsie 例子后 , 我们再看这个 promise 变成什么了

let x = 1
let p1 = new Promise((resolve, reject) => {
  if (x < 2) {
    resolve(x)
  } else {
    reject(x)
  }
})
p1.then(value => {
  console.log('value:' + value) // value:1
}, reason => {
  console.log('reason:' + reason)
})
console.log(p1)

我们看到 PromiseState 变成了 fulfilled 而 PromiseResult 变成了 1

Promise及相关知识细学_第3张图片

这好像没什么大不了的嘛 , 乍一看就跟乍一看一样 , 好像理所当然应该是这样的

接下来我们将 prototype 点开 ,看一看原型上有哪些方法

Promise及相关知识细学_第4张图片

原型上的方法不多嗷 , 我们也只需要关注 catch finally 和 then 方法就够了
ok 那么我们已经完全看到了 promise 实例的相关内容 , 接下来我们来看一看 promise 构造器上有什么内容
点开 constructor

Promise及相关知识细学_第5张图片

构造器上的方法我们需要关注的就是 all allSettled any race reject 和 resolve 方法

好啦 , 这下 promise 已经被你完全看完了 , 到这里大致混个眼熟就 ok , 接下来才开始学习

2.3 基础知识

可能你会有所疑惑 , 所以接下来介绍一下基础知识

一、PromiseState

一个 promise 有三个状态 , 分别是 pending , fulfilled 和 rejected

  1. pending 是一个 promise 的基础状态 , 所有 promise 一开始都是 pending 状态
    理解为 一个承诺即将发生
  2. fulfilled 表示这个 promise 已完成并且执行成功的回调函数
    理解为 一个承诺守约履行了
  3. rejected 表示这个 promise 失败了并且执行失败的回调函数
    理解为 一个承诺毁约拒绝了

特别注意:

  • 一个 promise 只能有一个状态 , 即一个承诺不会 即将发生 \ 守约 \ 毁约 同时出现
  • 状态 能从 pending 向 fulfilled rejected 改变
  • 状态一旦发生改变 , 将不会发生第二次改变 , 即状态固化了

二、PromiseResult

顾名思义 , 其实指的就是一个 promise 的结果值

  • 承诺守约履行了值就为 成功回调传入的参数
  • 承诺毁约拒绝了值就为 失败回调传入的参数
  • 由于状态不会发生二次改变 , 所以结果值是不会变的 , 无论多少次调用都会是同一结果

三、promise中的两个参数函数

我们看以下代码

let x = 1;
let p1 = new Promise((resolve, reject) => {
  if (x < 2) {
    resolve(x);
  } else {
    reject('出错了');
  }
});

p1 是我们新实例化的一个 promise 对象 , 和正常实例化一样的是 , 我们在初始化时传递了一个参数进去 , 只不过这个参数是一个函数 , 喏就是下面这个函数 , 是不是这样看起来就好很多

(resolve, reject) => {
  if (x < 2) {
    resolve(x);
  } else {
    reject('出错了');
  }
}

这个函数一共有两个参数 , resolve 和 reject , 而这两个参数恰好又是两个函数 , 只不过这两个函数不是我们定义的 , 我们在这里写的 resolve 和 reject 只是两个形参 , 你可以随便改名 , 叫啥都行

在 promise 的构造器中会自动调用我们传入的这个函数 , 同时会将真正的实参函数 resolve 和 reject 传入进来

而在我们的函数体中 , 当我们想要 履行承诺 时就调用 resolve 方法 , 并且将想要传递的值作为参数放入 ; 想要 拒绝承诺 时就调用 reject 方法 , 并且将拒绝的理由作为参数放入 ; 而无论是成功还是失败 , 我们放入的参数都会成为最终的 PromiseResult

同时 , 刚刚才学过的 PromiseState 知识告诉我们 , 我们调用 resolve 方法后状态就会从 pending 变成 fulfilled , 调用 reject 方法后状态就会从 pending 变成 rejected

你可以看到在我们上面的代码中 , x = 1 , 所以 x < 2 是成立的我们想要的结果 , 那么我们就 履行承诺 调用resolve 方法并且把 x 传入同时状态变成了 fulfilled , 否则呢我们就 拒绝承诺 调用 reject 方法并且把我们的拒绝理由 出错了 传入同时状态变成了 rejected

总的来说就是 , 这两个参数函数和我们无关 , 我们只要达到目的的时候调用一下就可以了

2.4 promise 的原型方法

好了 , 基本上已经了解的差不多了 , 接下来我们说说原型上的方法

2.4.1 then() 方法

首先我们要先弄清楚什么叫履行承诺 , 什么叫拒绝承诺

第一步是我给出承诺 , 如果我履行就干嘛干嘛 , 如果我拒绝就怎样怎样

第二步是你看我的行为 , 我到底是履行了还是拒绝了 , 我履行了你就怎样怎样 , 我拒绝了你就干嘛干嘛

其实就是一个根据上一个行为做出下一个行为

我们来看以下代码

let x = 1
let p1 = new Promise((resolve, reject) => {
  if (x < 2) {
    resolve(x)
  } else {
    reject('出错了')
  }
})
p1.then(value => {
  console.log('value:' + value)
}, reason => {
  console.log('reason:' + reason)
})

在这段代码的使用过程中呢 , 我们可以看到 then() 方法接收两个参数 , 而第一个参数就是成功的回调 , 当一个 promise 使用 resolve 方法时 , then() 方法就会进入第一个参数函数 , 同时将 resolve 方法中的参数传入 ;
当一个 promise 使用 reject 方法时 , then() 方法就会进入第二个参数函数 , 同时将 reject 方法中的参数传入

上面的代码中 , 我们调用的是 resolve 方法 , 所以 x 作为参数传到了第一个函数中替代了 value 形参 , 所以就打印了 value: 1

同样的如果将上面代码中的 x 修改为 10 , 就会调用 reject 方法 , 出错了 三个字就会作为参数传到第二个函数中替代 reason 形参 , 打印出 reason: 出错了

这就是 then() 方法了 , 是不是又简单又好用

2.4.2 catch() 方法

我们已经了解到了简单好用的 then() 方法 , 但是我还是觉得一个参数中写两个函数很难受 , 别问问就是难受 , 那怎么办呢

那当然就看看 catch() 方法了 , 我们来看以下代码

let x = 10
let p1 = new Promise((resolve, reject) => {
  if (x < 2) {
    resolve(x)
  } else {
    reject('出错了')
  }
})
p1.then(value => {
  console.log('value:' + value)
}).catch(err => {
  console.log('err:' + err)
})

可以看到 , 在 then() 中我只写了一个参数函数 , 那么按理说我们应该获取不到拒绝承诺的结果的 , 但是我们在后面又补上了一个 catch() 方法 , 同时 catch() 方法中的参数也是一个函数 , 所以 catch() 方法就是专门用来捕获拒绝的承诺 , 这样相比在 then() 中写两个函数要清晰明了很多 , 我们看看控制台中打印了什么

Promise及相关知识细学_第6张图片

当然了 , 你可能会问 , 那我都写呗 , 当然没问题啦 , 只不过都写的话 , catch() 方法就会失效

let x = 10
let p1 = new Promise((resolve, reject) => {
  if (x < 2) {
    resolve(x)
  } else {
    reject('出错了')
  }
})
p1.then(value => {
  console.log('value:' + value)
}, reason => {
  console.log('reason:' + reason)
}).catch(err => {
  console.log('err:' + err)
})

Promise及相关知识细学_第7张图片

所以可以认为 catch() 方法是 then() 方法的语法糖 , 当正主出来的时候 , 替身自然就要下场了

注意
你可能已经注意到了 , 在上面的代码中 , 我们是在 then() 方法的末尾直接使用点语法调用的 catch() 方法 , 因此我们意识到每个方法都会返回一个新的 promise , 这样才能达到不断链式调用的效果

2.4.3 finally() 方法

顾名思义啊 , 这个 finally() 方法呢就是最后执行的方法 , 而且是无论如何都会执行的方法 , 不接收参数

let x = 10
let p1 = new Promise((resolve, reject) => {
  if (x < 2) {
    resolve(x)
  } else {
    reject('出错了')
  }
})
p1.then(value => {
  console.log('value:' + value)
}).catch(err => {
  console.log('err:' + err)
}).finally(a => {
  console.log('finally:', a)
})

Promise及相关知识细学_第8张图片

我们看到 finally 打印出来的 a 是 undefined , 所以 finally() 方法是没有参数的 , 而且一定会执行

注意
finally() 方法也会返回一个新的 promise , 所以同样可以链式调用

p1.then(value => {
 console.log('value:' + value)
}).finally(a => {
 console.log('finally:', a)
}).catch(err => {
  console.log('err:' + err)
})

2.5 Promise 的静态方法

前面需要学习的原型方法就是那三个啦 , 接下来学习一下静态方法 , 也就是使用 static 声明在类中的方法

2.5.1 Promise.resolve() 方法

静态方法的话呢我觉得还是从 Promise.resolve() 方法开始学起最为简单 , 因为他的功能最简单

效果 : 将参数包装为 promise 对象

当然了 , 这里的参数需要分情况处理

  1. 参数为 promise 对象

    如果参数本身就已经是一个 promise 对象了 , 那就原封不动的返回这个 promise 对象

    let p1 = new Promise((resolve, reject) => { });
    let p2 = Promise.resolve(p1);
    console.log(p1 === p2); // true
    
  2. 参数是普通数据

    参数是数字 , 字符串 , 布尔值 , undefined , null , 数组 , 对象 , Set , Map 时则返回一个新的Promise对象,且 PromiseState 状态为 fulfilled , PromiseResult 值为参数值

    console.log(Promise.resolve(123))
    console.log(Promise.resolve('shaoyahu'))
    console.log(Promise.resolve(false))
    console.log(Promise.resolve(undefined))
    console.log(Promise.resolve(null))
    console.log(Promise.resolve([1, 3]))
    console.log(Promise.resolve({ name: 'shaoyahu' }))
    console.log(Promise.resolve(new Set([1, 2])))
    console.log(Promise.resolve(new Map([[1, 1], [2, 2]])))
    

    Promise及相关知识细学_第9张图片

  3. 没有参数

    没有参数就直接返回一个空值的 promise

    let p1 = Promise.resolve()
    let p2 = Promise.resolve(undefined)
    console.log(p1)
    console.log(p2)
    console.log(p1 === p2)
    

    Promise及相关知识细学_第10张图片

2.5.2 Promise.reject() 方法

用法和 Promise.resolve() 方法一样 , 只不过返回值的状态为 rejected , 其他没有区别

2.5.3 Promise.all() 方法

效果 : 对多个原有的 promise 包装进一个新的 promise 中 , 而这个新的 promise 只有 所有的 promise 全部状态变为 fulfilled 或者 任意一个 promise 状态变为 rejected 时才会从 pending 状态变为相应的状态

all() 方法接收的参数是一个数组 , 将所有的 promise 对象放在数组中 , 如果出现不是 promise 对象的参数 , 则默认使用 Promise.resolve() 方法进行包裹

简单来说就是所有的 promise 成功就成功 , 有一个失败就失败 , 我们看以下代码

let p1 = new Promise((resolve, reject) => {
  resolve('p1')
})
let p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('p2')
  }, 2000);
})
let p3 = new Promise((resolve, reject) => {
  resolve('p3')
})
let p = Promise.all([p1, p2, p3, 123])
console.log(p)
p.then(value => {
  console.log(value)
  console.log(p)
})

我们来看看结果 , 很显然 , 一开始 p 是处于 pending 状态的 , 等数组中所有的 promise 都返回成功之后才改变为 fulfilled 状态 , 并且返回值是每个 promise 返回值组成的数组

Promise及相关知识细学_第11张图片

如果有一个 promise 失败了 , 那么返回的就会是 rejected 状态的 p , 同时返回值是失败的那个 promise 的失败原因

2.5.4 Promise.race() 方法

这个方法和 all() 方法其实差不多 , 只不过你看名字是 race , 竞速嘿嘿

效果 : 将多个 promise 实例 , 包装成一个新的 promise 实例 , 一旦迭代器中的某个 promise 成功或者失败 , 那么新的 promise 就会立即成功或者失败

简单来说就是只取返回最快的值,无论是成功还是失败 , 我们看以下代码

let p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('p1')
  }, 1500);
})
let p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('p2')
  }, 2000);
})
let p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('p3')
  }, 1000);
})
let p = Promise.race([p1, p2, p3])
console.log(p)
p.then(value => {
  console.log(value)
  console.log(p)
})

我们从代码中可以看出 , 最快的应该是 p3 , 所以结果肯定就是 p3 了

Promise及相关知识细学_第12张图片

2.5.5 Promise.any() 方法

效果 : 对多个原有的 promise 包装进一个新的 promise 中 , 原有的 promise 中一旦有一个成功了 , 就返回成功的那个 promise 值, 并且新的 promise 状态改为 fulfilled

简单来说 , 就是 Promise.all() 方法的眼瞎版 , 不用看失败的只看成功的 , 是 Promise.race() 方法的偏心版 , 我只拿第一个 , 但是我只看成功的 , 我们看以下代码

let p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('p1')
  }, 1500);
})
let p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('p2')
  }, 2000);
})
let p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('p3')
  }, 1000);
})
let p = Promise.any([p1, p2, p3])
console.log(p)
p.then(value => {
  console.log(value)
  console.log(p)
})

p3 是最快的 , 但是它是失败的 , 所以返回值是最快成功的 p1

Promise及相关知识细学_第13张图片

2.5.6 Promise.allSettled() 方法

效果 : 对多个原有的 promise 包装进一个新的 promise 中 , 当所有的 promise 状态都发生改变时 , 新的 promise 状态改为 fulfilled , 并且返回值是原有 promise 返回值组成的数组加工后的值

简单来说 , 就是 Promise.all() 方法忽略了失败 , 将所有的结果都返回过来 , 我们看以下代码

let p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('p1')
  }, 1500);
})
let p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('p2')
  }, 2000);
})
let p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('p3')
  }, 1000);
})
let p = Promise.allSettled([p1, p2, p3])
console.log(p)
p.then(value => {
  console.log(value)
  console.log(p)
})

我们直接看返回数据 , 你一下就会懂了 , 很清晰的

Promise及相关知识细学_第14张图片

3. 总结

Promise 作为前端必须要学习的一个重要知识点 , 想必大家上面的内容其实都会吧 , 我真是班门弄斧啊 , 那么之后 , 我们来重写一个 promise 类试试看吧

4. 结束

学到这里 , 终于可以松一口气了 , 怎么样 , 是不是已经完全掌握了呢 ?
其实还是挺有难度的 , 难度在真正用起来可能会比较绕 , 但是方法就这些 , 除非你只是在调用接口

如果你跟着做遇到了什么问题无法实现 , 请在评论区或者私信告诉我 , 我发动网友给你解答

你可能感兴趣的:(javascript,开发语言,ecmascript,前端)