igCircle Blog

Promise基础

2022年8月19日

15分钟阅读

4 次浏览

0 条评论

前端三大件

标签

promise

介绍 promise是什么、怎么使用、实现原理

1 Promise 的理解

1.1 promise 是什么

1.1.1 理解

  1. 抽象表达

    • Promise 是一门新的技术(ES6 规范)
    • Promise 是 JS 中进行一部编程的新的解决方案(旧方案是单纯使用回调函数)
  2. 具体表达

    • 从语法上来说:Promise 是一个构造函数
    • 从功能上来说:Promise 对象用来封装一个一部操作并可以获取其成功/失败的结果值

1.1.2 异步编程的例子

  • fs 文件操作(fs 为 node.js 下的一个模块,可以对磁盘进行读写操作)

    js
    1const fs = require('fs')
    2/* 
    3      fs.readFile('./resource/content.txt',(err,data)=>{
    4        //如果错误则抛出错误
    5        if(err) throw err;
    6        //否则输出文件内容
    7        console.log(data.toString());
    8            });
    9 */
    10
    11//Promise形式
    12let p = new Promise((resolve, reject) => {
    13 fs.readFile('./resource/content.txt', (err, data) => {
    14  //如果错误则抛出错误
    15  if (err) reject(err)
    16  //如果成功
    17  resolve(data)
    18 })
    19})
    20
    21//调用then方法
    22p.then(
    23 (value) => {
    24  console.log(value.toString())
    25 },
    26 (reason) => {
    27  console.log(reason)
    28 }
    29)
  • 数据库操作(mongoDB,MySQL 等)

  • AJAX

    js
    1$.get('/server', (data) => {})
  • 定时器

    js
    1setTimeout(() => {}, 2000)

1.2 为什么要使用 Promise

1.2.1 指定回调函数的方式更加灵活

  • 旧的:必须在启动异步任务之前指定
  • promise:启动异步 => 返回 promise 对象 => 给 promise 对象绑定回调函数(甚至可以在异步任务结束后指定多个)

1.2.2 支持链式调用,可以解决回调地狱问题

  1. 回调地狱:回调函数嵌套调用,外部回调函数异步执行的 结果是嵌套的回调执行的条件
  2. 回调地狱的缺点
    • 不便于阅读
    • 不便于异常处理
  3. 解决方案:promise 链式调用
  4. 终极解决方案:async/await

1.3 Promise 的状态

  1. 实例对象中的一个属性 『PromiseState』
  • pending 未决定的
  • resolved / fullfilled 成功
  • rejected 失败
  1. promise 的状态改变
    • pending 变为 resolved/fullfilled
    • pending 变为 rejected
  • 只有这两种变化可能,也就是说不可能由 resolved 变为 rejected,且一个 promise 对象只能改变一次

  • 无论变为成功还是失败,都会有一个结果数据

  • 成功的结果数据一般称为 value,失败的结果数据一般称为 reason

1.4 Promise 对象的值

实例对象中的另一个属性 『PromiseResult』 保存着异步任务『成功/失败』的结果,下面两个函数可以修改对返回的结果进行修改,其他皆不可以

  • resolve()
  • reject ()

2 Promise 的使用

2.1 API

  1. Promise 构造函数:Promise(excutor){}
    • executor 函数:执行器(resolve,reject) => {}
    • resolve 函数:内部定义成功时我们调用的函数 value => {}
    • reject 函数:内部定义失败时我们调用的函数 reason => {}
  • 说明:executor 会在 Promise 内部立即同步调用,异步操作在执行器中执行
js
1let p = new Promise((resolve, reject) => {
2 //同步调用
3 console.log(111)
4})
5console.log(222)
  1. Promise.prototype.then 方法:(onResolved,onReject) => {}
    • onResolved 函数:成功的回调函数 (value) => {}
    • onReject 函数:失败的回调函数 (reason) => {}
  • 说明:指定用于得到成功 value 的成功回调和用于得到失败 reason 的失败回调返回一个新的 promise 对象
  1. Promise.prototype.catch 方法:(onRejected) => {}
    • onRejected 函数:失败的回调函数 (reason) =>{}
  • 说明:then()的语法糖,相当于:then(undefined,onRejected)

    js
    1let p = new Promise((resolve, reject) => {
    2 //修改promise对象的状态
    3 reject('error')
    4})
    5//执行catch方法
    6p.catch((reason) => {
    7 console.log(reason) //输出error
    8})
  1. Promise.resolve 方法:(value) => {}
    • value:成功的数据或 promise 对象
  • 说明:返回一个成功/失败的 promise 对象

    js
    1let p1 = Promise.resolve(123)
    2//如果传入的参数为非Promise类型的对象,则返回的结果皆为成功的promise对象
    3console.log('p1:', p1)
    4
    5//如果传入的参数为Promise对象,则参数的结果决定resolve的结果
    6let p2 = Promise.resolve(
    7 new Promise((resolve, reject) => {
    8  //成功的结果
    9  resolve('OK')
    10 })
    11)
    12console.log('p2:', p2)
    13
    14let p3 = Promise.resolve(
    15 new Promise((resolve, reject) => {
    16  //失败的结果
    17  reject('error')
    18 })
    19)
    20console.log('p3:', p3)

  1. Promise.reject 方法: (reason) => {}
    • reason:失败的原因或 promise 对象
  • 说明: 返回一个失败的 promise ==对象==

    js
    1let p1 = Promise.reject(456)
    2//如果传入的参数为非Promise类型的对象,则返回的结果皆为成功的promise对象
    3console.log('p1:', p1)
    4
    5//如果传入的参数为Promise对象,则参数的结果决定resolve的结果
    6let p2 = Promise.reject(
    7 new Promise((resolve, reject) => {
    8  resolve('OK')
    9 })
    10)
    11//即使传入的时一个成功的promise对象,返回的也是失败的promise对象
    12console.log('p2:', p2) //结果PromiseResult是传入的参数OK
    13
    14let p3 = Promise.reject(
    15 new Promise((resolve, reject) => {
    16  //失败的结果
    17  reject('error')
    18 })
    19)
    20console.log('p3:', p3)

  1. Promise.all 方法:(promises) =>{}
    • promises:包含 n 个 promise 的数值
  • 说明:返回一个新的 promise,只有所有的 promise 都成功了才成功,只有有一个失败了就直接失败

    js
    1let p1 = new Promise((resolve, reject) => {
    2 resolve('OK')
    3})
    4let p2 = Promise.resolve('Success')
    5let p3 = Promise.resolve('Oh Yeah')
    6let p4 = Promise.reject('Error') //失败的
    7
    8//成功的情况
    9const resultSuccess = Promise.all([p1, p2, p3])
    10console.log('resultSuccess:', resultSuccess)
    11
    12//失败的情况
    13const resultError = Promise.all([p1, p2, p4])
    14console.log('resultError:', resultError)

  1. Promise.race 方法:(promises) =>{}
    • promises:包含 n 个 promise 的数组
  • 说明:返回一个新的 promise,第一个完成的 promise 的结果状态就是最终的结果状态
js
1let p1 = new Promise((resolve, reject) => {
2 resolve('OK')
3})
4let p2 = Promise.resolve('Success')
5let p3 = Promise.resolve('Oh Yeah')
6
7let p4 = new Promise((resolve, reject) => {
8 //添加定时器,异步任务
9 setTimeout(() => {
10  resolve('OK')
11 }, 1000)
12})
13let p5 = Promise.reject('Error') //失败的结果
14//调用
15const result1 = Promise.race([p1, p2, p3])
16console.log('result1', result1)
17
18const result2 = Promise.race([p4, p2, p3])
19console.log('result2', result2)
20
21const result3 = Promise.race([p4, p5, p1])
22console.log('result3', result3)
23
24const result4 = Promise.race([p5, p1, p2])
25console.log('result4', result4)

2.2 Promise 的几个关键问题

  1. 如何改变 promise 的状态

    • resolve(value):如果当前是 pending 就会变为 resolved
    • reject(reason): 如果当前是 pending 就会变为 rejected
    • 抛出异常: 如果当前是 pending 就会变为 rejected
  2. 一个 promise 指定多个成功/失败回调函数, 都会调用吗?

    • 当 promise 改变为对应状态时都会调用
    js
    1let p = new Promise((resolve, reject) => {
    2 resolve('OK') //改变状态
    3})
    4
    5///指定回调 - 1
    6p.then((value) => {
    7 console.log(value)
    8})
    9
    10//指定回调 - 2
    11p.then((value) => {
    12 alert(value)
    13})
  1. 改变 promise 状态和指定回调函数谁先谁后?

(1)都有可能, 正常情况下是先指定回调再改变状态, 但也可以先改状态再==指定==回调

js
1//先改变状态,再指定回调
2let p1 = new Promise((resolve, reject) => {
3 resolve('OK') //同步任务
4})
5
6p1.then(
7 (value) => {
8  console.log(value)
9 },
10 (reason) => {}
11)
12
13//先指定回调,再改变状态
14let p2 = new Promise((resolve, reject) => {
15 //异步任务
16 setTimeout(() => {
17  resolve('OK')
18 }, 3000)
19})
20
21//then先执行,而不是then里面的回调函数先执行,所以value会再3s后输出
22p2.then(
23 (value) => {
24  console.log(value) //状态改变才能拿到数据
25 },
26 (reason) => {}
27)

(2)如何先改状态再指定回调?

  • 在执行器中直接调用 resolve()/reject()

  • 延迟更长时间才调用 then()

(3) 什么时候才能得到数据?

  • 如果先指定的回调, 那当状态发生改变时, 回调函数就会调用, 得到数据
  • 如果先改变的状态, 那当指定回调时, 回调函数就会调用, 得到数据
  1. promise.then()返回的新 promise 的结果状态由什么决定?
  • 简单表达: 由 then()指定的回调函数执行的结果决定

  • 详细表达:

    • 如果抛出异常,新 promise 变为 rejected,reason 为抛出的异常
    • 如果返回的是非 promise 的任意值,新 promise 变为 resolved,value 为返回值
    • 如果返回的是另一个新的 promise,此 promise 的结果就会成为新 promise 的结果
js
1let p = new Promise((resolve, reject) => {
2 resolve('ok')
3})
4//执行 then 方法
5let result = p.then(
6 (value) => {
7  // console.log(value);
8  //1. 抛出错误
9  // throw '出了问题';
10  //2. 返回结果是非 Promise 类型的对象
11  // return 123;
12  //3. 返回结果是 Promise 对象
13  return new Promise((resolve, reject) => {
14   resolve('success')
15   // reject('error');
16  })
17 },
18 (reason) => {
19  console.warn(reason)
20 }
21)
22
23console.log(result)
  1. promise 如何串连多个操作任务?
  • promise 的 then()返回一个新的 promise, 可以看成 then()的链式调用

  • 通过 then 的链式调用串连多个同步/异步任务

    js
    1let p = new Promise((resolve, reject) => {
    2 setTimeout(() => {
    3  resolve('OK')
    4 }, 1000)
    5})
    6
    7p.then((value) => {
    8 return new Promise((resolve, reject) => {
    9  resolve('success')
    10 })
    11})
    12 .then((value) => {
    13  console.log(value) //success
    14 })
    15 .then((value) => {
    16  console.log(value)
    17  //undefined,因为第二个then没有return,所以return undefined,所以
    18  //第二个then的返回结果是一个成功的promise且成功的结果是undefined,而不是返回一个promise对象
    19  //所以第三个then会输出第二个then返回的结果
    20 })
  1. promise 异常传透?
  • 当使用 promise 的 then 链式调用时, 可以在最后指定失败的回调,前面任何操作出了异常, 都会传到最后失败的回调中处理
js
1let p = new Promise((resolve, reject) => {
2 setTimeout(() => {
3  resolve('OK')
4  // reject('Err');
5 }, 1000)
6})
7
8p.then((value) => {
9 // console.log(111);
10 throw '失败啦!'
11})
12 .then((value) => {
13  console.log(222)
14 })
15 .then((value) => {
16  console.log(333)
17 })
18 .catch((reason) => {
19  //catch方法最后对错误进行统一的处理
20  console.warn(reason)
21 })
  1. 中断 promise 链?
  • 当使用 promise 的 then 链式调用时, 在中间中断, 不再调用后面的回调函数

  • 办法: 在回调函数中返回一个 pendding 状态的 promise 对象(有且只有一个方式)

    js
    1let p = new Promise((resolve, reject) => {
    2 setTimeout(() => {
    3  resolve('OK')
    4 }, 1000)
    5})
    6
    7p.then((value) => {
    8 console.log(111)
    9 //有且只有一个方式
    10 return new Promise(() => {}) //返回以恶pending状态的promise对象,状态不改变,后面的回调不再执行
    11})
    12 .then((value) => {
    13  console.log(222)
    14 })
    15 .then((value) => {
    16  console.log(333)
    17 })
    18 .catch((reason) => {
    19  console.warn(reason)
    20 })

3 自定义(手写)Promise

这部分看文件夹中的代码

4 async 与 await

4.1 MDN 文档

async

await

4.2 async 函数

  1. 函数的返回值为 promise 对象
  2. promise 对象的结果由 async 函数执行的返回值决定
js
1//then
2async function main() {
3 //1. 如果返回值是一个非Promise类型的数据
4 // return 521;
5 //2. 如果返回的是一个Promise对象
6 // return new Promise((resolve, reject) => {
7 //     // resolve('OK');
8 //     reject('Error');
9 // });
10 //3. 抛出异常
11 throw 'Oh NO'
12}
13
14let result = main()
15
16console.log(result)

4.3 await 表达式

  1. await 右侧的表达式一般为 promise 对象, 但也可以是其它的值
  2. 如果表达式是 promise 对象, await 返回的是 promise 成功的值
  3. 如果表达式是其它值, 直接将此值作为 await 的返回值
js
1async function main() {
2 let p = new Promise((resolve, reject) => {
3  // resolve('OK');
4  reject('Error')
5 })
6 //1. 右侧为promise的情况,await 返回的是 promise 成功的值
7 // let res = await p;
8 // console.log(res);
9
10 //2. 右侧为其他类型的数据,直接将此值作为 await 的返回值
11 let res2 = await 20
12 console.log(res2)
13
14 //3. 如果promise是失败的状态,用try catch进行捕获
15 try {
16  let res3 = await p
17 } catch (e) {
18  console.log(e)
19 }
20}
21
22main()

4.4 注意

  1. await 必须写在 async 函数中, 但 async 函数中可以没有 await
  2. 如果 await 的 promise 失败了, 就会抛出异常, 需要通过 try...catch 捕获处理

4.5 async 与 await 结合使用

js
1/**
2 * resource  1.html  2.html 3.html 文件内容
3 */
4
5const fs = require('fs')
6const util = require('util')
7const mineReadFile = util.promisify(fs.readFile)
8
9// //回调函数的方式
10// fs.readFile('./resource/1.html', (err, data1) => {
11//     if(err) throw err;
12//     fs.readFile('./resource/2.html', (err, data2) => {
13//         if(err) throw err;
14//         fs.readFile('./resource/3.html', (err, data3) => {
15//             if(err) throw err;
16//             console.log(data1 + data2 + data3);
17//         });
18//     });
19// });
20
21//async 与 await
22async function main() {
23 try {
24  //读取第一个文件的内容
25  let data1 = await mineReadFile('./resource/1.html')
26  let data2 = await mineReadFile('./resource/2.html')
27  let data3 = await mineReadFile('./resource/3.html')
28  console.log(data1 + data2 + data3)
29 } catch (e) {
30  console.log(e.code)
31 }
32}
33
34main()

5 参考

尚硅谷 Promise 教程

6 代码

GitHub 地址

喜欢这篇文章吗?

加载中...

评论

0

登录后即可参与评论讨论

加载评论中...

相关文章

call bind apply的区别以及源码的手写

call方法的第一个参数也是this的指向,后面传入的是一个参数列表改变this指向后原函数会立即执行,且此方法只是临时改变this指向一次 当第一个参数为null、undefined的时候,默认指向window(在浏览器中) `js function fn(...args) { console.log(this的指向为${this}, args) } let obj = { ...

前端三大件
js手写

2025-08-06

2

0

目录