likes
comments
collection
share

JavaScript高级--Promise①(概念与使用)

作者站长头像
站长
· 阅读数 12

Promise的概念

Promise A+ 规范

  • 根据 Promise A+ 规范所言,Promise 是一个带有 then 方法的对象或函数
  • 只要满足 Promise A+ 规范,其就是 Promise
 // 对象
 const Promise = {
   then(...){}
 }
 ​
 // 函数
 function Promise(){}
 Promise.then = function(...){}

JavaScript高级--Promise①(概念与使用)

  • 只要是一个 Promise,就可以与另一个 Promise 进行互操作

ES6 中的 Promise 构造函数

  • ES6 中的 Promise 构造函数,称之为对 Promise A+ 规范的实现
  • 通过该构造函数可以构建一个 promise 实例
 new Promise()
  • 但是严格来说该构造函数本身并不是一个 Promise,因为它没有 then 方法,但是通过该构造函数创建的实例带有满足 Promise A+then 方法
 const p = new Promise()
 p.then(...)

为何需要Promise

  • 一项新技术的诞生,目的是为了解决旧技术的存在的痛点和缺陷
  • 在没有 Promise 的时代,一些异步操作通常需要通过回调函数去获取异步操作的结果
  • 当异步操作变得繁琐,或者获取异步操作之后还需要进行另一个异步操作,那么回调函数就会层层嵌套,形成回调地狱

回调地狱

  • 回调地狱是形容在异步编程中出现的多层嵌套回调函数的现象
  • 当异步操作依赖于前一个操作的结果时,为了保证顺序和正确性,常常会使用回调函数来处理这些操作,如果嵌套的函数层级过多,代码会变得冗余,耦合性强,并且难以维护
 request('/login', (res) => {
   console.log('登录成功');
   request('/user/info', (res) => {
     console.log('获取用户信息');
     request('/home/list', (res) => {
       console.log('获取首页数据');
       request('xxx', (res) => {
           ...
       })
     });
   });
 });

Promise 的出现解决的痛点

  • 解决回调地狱: 通过 then 方法的链式操作,让回调嵌套的变成链式调用,使得代码更加直观
  • 个人理解: Promise 并没有真正意义上解决回调地狱的嵌套问题,而是让回调方式变得更加直观
 const promise = new Promise()
 ​
 promise.then(...)
        .then(...)
        .then(...)
  • 统一 JavaScript 中的异步方案: 提供简洁的链式调用和简易的捕获错误机制,而且支持异步并发执行
 const promise = new Promise()
 ​
 promise.then(...).catch(...)

Promise的基本使用

  • Promise 构造函数的使用语法如下
 const promise = new Promise((resolve, reject) => {
   // 异步任务操作...
 });
  • Promise 接受一个函数作为参数,该函数被称为 executor,在 new Promise 时,executor 会立即自动执行,executor 函数还提供两个函数作为参数

executor 函数的两个参数

  • resolve(value) 当任务成功完成时调用,并附带成功结果 value
  • reject(error) 当任务出现错误时调用,并抛出错误结果 error

获取 promise 实例返回的结果

  • 当调用 resolve 回调函数时,会执行 Promise 实例的 then 方法传入的回调函数
  • 当调用 reject 回调函数时,会执行 Promise 实例的 catch 方法传入的回调函数
 const promise1 = new Promise((resolve, reject) => {
   reslove('success')
 });
 promise1.then((res) => {
   console.log(res) // 'success'
 })
 ​
 const promise2 = new Promise((resolve, reject) => {
   reject('error')
 });
 promise2.catch((err) => {
   console.log(err) // 'error'
 })
  • 也可以通过 then 方法传入的第二个回调函数捕获错误,相当于使用 catch 捕获错误
 const promise = new Promise((resolve, reject) => {
   reject('error');
 });
 ​
 promise3.then(
   (res) => {
     console.log(res); 
   },
   (err) => {
     console.log(err); // 'error'
   }
 );
 ​
 // 以上写法和下面是一致的
 promise3.then((res) => {
   console.log(res); 
 })
 promise3.catch((err) => {
    console.log(err); // 'error'
 })

总结:

  • new Promise() 后,executor 函数自动执行
  • 执行成功则调用 resolve(value),通过 then 方法传入的第一个函数获取成功结果
  • 执行出错后则调用 reject(err),通过 then 方法传入的第二个函数(或 catch 方法)捕获错误结果

Promise的三种状态

  • Promise 的使用过程中划分为三个状态pending(待定)fulfilled(已兑现)rejected(已拒绝)
 const promise = new Promise((resolve, reject) => {
   // pending
   if(success) {
     resolve('success'); 
   }else {
     reject('error')
   }
 })
 ​
 promise.then((res) => {
   // fulfilled
   console.log(res);
 })
 promise.catch((err) => {
   // rejected
   console.log(err);
 })
  • 待定(pending): 初始状态,既没有被兑现也没有被拒绝,当执行 executor 中的代码时处于该状态
  • 已兑现(fulfilled): 意味着操作成功完成,调用 resolve 后处于该状态
  • 已拒绝(rejected): 意味着操作失败,调用 reject 后或抛出异常处于该状态

JavaScript高级--Promise①(概念与使用)

注意:Promise 状态一旦确定就是不可更改的(锁定)

resolve参数

resolve 函数的参数可以有多种格式

  • 普通值或对象,参数可在 then 方法的回调中获得,状态由 pending --> fulfilled
 new Promise((resolve, reject) => {
   resolve({ name:'Jimmy' });
 }).then((res) => { 
   console.log(res); // '{ name:'Jimmy' }',此时状态为fulfilled
 })
  • 传入一个 Promise,那么当前 Promise 的状态将由传入的 Promise 来决定,相当于状态进行移交
 const newPormise = new Promise((resolve, reject) => {
   reject('error'); // 拒绝
 });
 ​
 new Promise((resolve, reject) => {
   resolve(newPormise); // 传入一个Promise,当前Promise状态取决于newPromise
 }).then(
   (res) => {
     console.log('res:', res);
   },
   (err) => {
     console.log('err:', err);
   }
 );
  • 传入带 then 方法的对象,那么该对象 then 方法会执行,且同时决定 Promise 状态
 const obj = {
   then(resolve, reject) {
     reject('error');
   }
 };
 ​
 new Promise((resolve, reject) => {
   resolve(obj);
 }).then(
   (res) => {
     console.log('res:', res);
   },
   (err) => {
     console.log('err:', err); // err: error
   }
 );

Promise对象方法

JavaScript高级--Promise①(概念与使用)

then()

  • then 方法接收两个回调函数作为参数,分别在状态更改为 fulfilledrejected 时调用

then 方法接收的两个参数

  1. fulfilled 的回调函数: 当状态变成 fulfilled 时会执行的函数
  2. rejected 的回调函数: 当状态变成 rejected 时会执行的函数
 const promise = new Promise((resolve, reject) => {
   resolve('success');
 });
 ​
 promise.then((res) => {
   console.log('res:', res); // 'res:success'
 },(err) => {
   console.log('err:', err);
 });

同一个 promise 对象可以被调用多次 then 方法

  • 当调用resolve 方法时,所有的 then 方法传入的回调函数都会被调用
 promise.then((res) => {
   console.log('res:', res); // res: success
 });
 promise.then((res) => {
   console.log('res2:', res); // res2: success
 });
 promise.then((res) => {
   console.log('res3:', res); // res3: success
 });

then 方法传入的回调可以有返回值,并且 then 方法本身的返回值是一个 Promise

  • 传入的回调返回普通值,该值会作为 then 方法本身返回的 Promiseresolve
 promise
   .then((res) => {
     return { name: 'Jimmy' };
   })
   .then((newRes) => {
     console.log(newRes); // { name: 'Jimmy' }
   });
  • 传入的回调返回 Promise,那么 then 方法本身返回的 Promise 的状态取决于回调中返回的 Promise
 promise
   .then((res) => {
     return new Promise((resolve, reject) => {
       reject(123); // ‘rejected’
     });
   })
   .then(
     (newRes) => {
       console.log(newRes);
     },
     (err) => { // 触发第二个回调
       console.log(err); // 123
     }
   );
  • 传入的回调返回带 then 方法的对象,那么 then 方法本身返回的 Promise 的状态取决于返回对象中的 then 方法
 promise
   .then((res) => {
     return {
       then(resolve, reject) {
         resolve(456);
       }
     };
   })
   .then((newRes) => {
     console.log(newRes); // 456
   });

catch()

  • executor函数调用 reject,或者抛出异常时,都会触发 catch 方法捕获错误
 const promise = new Promise((resolve, reject) => {
   reject('error');
 });
 ​
 promise.catch((err) => {
   console.log(err); // error
 });
  • 无论是在 Promiseexecutor 函数,还是其对象的 then 方法中,只要有 reject() 或异常抛出,catch 方法都会捕获
 const promise = new Promise((resolve, reject) => {
   resolve('success');
 });
 ​
 promise
   .then((res) => {
     console.log(res); // 'success'
     throw new Error('error');
   })
   .catch((err) => {
    // 捕获then方法中返回的promise抛出的错误
     console.log(err); // Error对象
   });
 const promise = new Promise((resolve, reject) => {
   reject('error');
 });
 ​
 promise
   .then((res) => {
     // reject调用,这里是不会执行的
     return new Promise((resolve, reject) => {
       resolve('success');
     });
   })
   .catch((err) => {
     // 这里捕获的是promise的异常,而不是then方法中返回的Promise的异常
     console.log(err); // error
   });
  • catch 方法也是可以有返回值的,并且本身也是返回一个 Promise
  • 当在 catch 方法中的回调返回内容后,返回的内容和 catch 本身返回的 Promise 的状态是有关联的
 const promise = new Promise((resolve, reject) => {
   reject('error');
 });
 ​
 promise
   .catch((err) => {
     return 'catch return value'; // 相当于在返回的Promise中resolve('catch return value')
   })
   .then((res) => {
     console.log('then:', res); // then: catch return value
   })
   .catch((err) => {
     console.log('catch:', err);
   });

finally()

  • finally 是在 ES2018 中新增特性,它表示无论 Promise 对象变成 fulfilled 还是 rejected 状态,该方法内的代码最终都会被执行
  • finally 方法不接收参数,因为无论前面是 fulfilled 状态,还是 rejected 状态,它都会执行
 const promise = new Promise((resolve, reject) => {
   resolve('resolve message');
 });
 ​
 promise
   .then((res) => {
     console.log('res:', res); // res: resolve message
   })
   .catch((err) => {})
   .finally(() => {
     console.log('finally code execute'); // finally code execute
   });
  • finally 的功能是设置一个处理程序在前面的操作完成后,执行清理,如关闭 loading,终止某些连接等
 showLoading(); // 显示loading遮罩
 promise
   .then((data) => {
     console.log('服务器返回的数据:', data);
   })
   .finally(() => {
     hideLoading(); // 请求结束,处理对应数据后,关闭loading遮罩
   });
  • 由于 finally 不处理 promise 的结果,所以可以将结果或错误传递给下一个合适的处理程序
 new Promise((resolve, reject) => {
   resolve('value');
 })
   .finally(() => console.log('Promise ready')) // 先触发
   .then((res) => console.log(res)); //  "value"
  • finally 方法的回调函数不返回任何内容,就算有返回值也会默认被忽略
 const promise = new Promise((resolve, reject) => {
   resolve('value');
 })
 ​
 promise
   .finally(() => 'finally return value';) // 返回值会被忽略
   .then((res) => console.log(res)); // 这里输出的是 "value"
  • 但是 finally 的回调函数可以抛出 error, 执行将转到最近的 error 的处理程序
 const promise = new Promise((resolve, reject) => {
   resolve('value');
 })
 ​
 promise
   .finally(() => { throw new Error('error'); }) // 先触发
   .catch((err) => console.log(err)); // 这里输出finally抛出的错误对象

Promise类方法

  • thencatchfinally 方法都属于 Promise 的实例方法,存在于 Promise.prototype
  • Promise 类上也有一些静态方法可以使用, 一共有 6 种静态方法

resolve()

  • 用结果 value 创建一个 fulfilled 状态的 promise,其用法相当于 new Promise 并执行 resolve
 Promise.resolve('success');
 ​
 // 相当于
 new Promise(resolve => resolve('success'))
  • 当已经确定返回结果,并且期望返回一个 Promise 时,可以使用该方法

Promise.resolve 的参数形态 :

  • 普通的值或者对象
 Promise.resolve('success');
  • 本身是 Promise,并且 resolve 方法本身返回的 Promise 状态会取决于参数中的 Promise
 const promise2 = Promise.resolve(
   new Promise((resolve, reject) => {
     reject('error');
   })
 );
 ​
 promise2.catch((err) => {
   console.log(err); // error
 });
  • then 方法的对象,该对象的 then 方法也决定 resolve 本身返回的 Promise 的状态
 const promise3 = Promise.resolve({
   then(resolve, reject) {
     reject('error');
   }
 });
 promise3.catch((err) => {
   console.log(err); // error
 });

reject()

  • error 创建一个 rejected 状态的 Promise
 Promise.reject('error message')
 ​
 // 等同于
 new Promise((resolve, reject) => reject('error message'))
  • reject 不分情况,不管传入的参数是什么,最后都会返回一个 rejected 状态的 Promise
  • 在开发中,这个方法几乎从未被使用过

all()

  • 有时候希望并行执行多个 promise,并等待所有 promise 都准备就绪后,再对数据进行处理
  • 这时候可以使用 Promise.all(),它接受一个可迭代对象(通常是一个 promise 数组),并返回一个包含结果的数组
  • 当所有给定的 promisefulfilled 状态时,Promise.all() 才完成
 const promise1 = new Promise((resolve) => setTimeout(() => resolve(1), 1500));
 const promise2 = new Promise((resolve) => setTimeout(() => resolve(2), 1000));
 const promise3 = new Promise((resolve) => setTimeout(() => resolve(3), 500));
 ​
 Promise.all([promise1, promise2, promise3]).then((res) => {
   console.log(res); // 3 秒之后输出 [ 1, 2, 3 ]
 });
  • 注意: 结果数组中元素的顺序与传入的 promise 顺序相同,即使第一个 promise 等待时间最长才 fulfilled,但仍是结果数组中的第一个
  • 如上所示,第一个放入的是等待 1500ms 才完成的 promise,在返回结果中它仍是第一个
  • 当传入的数组中包含不是 Promise 的项,其内部会使用 Promise.resolve 进行转换
 Promise.all([promise1, promise2, promise3, 'abc']).then((res) => {
   console.log(res); // [ 1, 2, 3, 'abc' ]
 });
  • 如果任意一个 promise 变成 rejected,那么 Promise.all 返回的 promise 的状态也是 rejected,并且将错误信息 error 返回
 const promise1 = new Promise((resolve, reject) => setTimeout(() => reject('error'), 1500));
 const promise2 = new Promise((resolve) => setTimeout(() => resolve(2), 1000));
 const promise3 = new Promise((resolve) => setTimeout(() => resolve(3), 500));
 ​
 Promise.all([promise1, promise2, promise3, 'abc']).catch((err) => {
   console.log(err); // error
 });
  • 注意: 如果其中一个 promise 状态变成 rejected,错误信息回立马返回,Promise.all 马上变成rejected,其他 promise 的结果均会被忽略,但操作仍需执行
 const promise1 = new Promise((resolve, reject) => setTimeout(() => reject('error message'), 500));
 const promise2 = new Promise((resolve) =>
   setTimeout(() => {
     console.log('会执行');// 这里会输出
     resolve(2);
   }, 3000)
 );
 const promise3 = new Promise((resolve) =>
   setTimeout(() => {
     console.log('会执行'); // 这里会输出
     resolve(3);
   }, 3000)
 );
 ​
 Promise.all([promise1, promise2, promise3, 'abc']).catch((err) => {
   console.log(err); // error message
 });

JavaScript高级--Promise①(概念与使用)

allSettled()

  • all 方法的缺陷: 当其中一个 Promise 变成 rejected 状态时,Promise.all 返回的新 Promise 会立即变成 rejected 状态,对于 fulfilled 或仍处于 pending 状态的 Promise,是无法获取对应结果的,而且也没有采取任何措施来取消
  • ES2020 中添加了新的API -- Promise.allSettled,该方法会在所有传入的 Promise 状态都变成 fulfilledrejected 后,才会有最终的状态,并且返回的 Promise 一定是 fulfilled
 const promise1 = new Promise((_, reject) => setTimeout(() => reject('error'), 500));
 const promise2 = new Promise((resolve) => setTimeout(() => resolve(2), 1000));
 const promise3 = new Promise((resolve) => setTimeout(() => resolve(3), 1500));
 ​
 Promise.allSettled([promise1, promise2, promise3]).then((res) => {
   console.log(res);
 });
  • 该方法仍然返回一个数组,但是该数组由对象组成,如 { status: "fulfilled", value: result }

JavaScript高级--Promise①(概念与使用)

  • 该方法在一些旧的浏览器中可能需要 polyfills,可以使用 all 方法实现 allSettled
 const rejectHandler = (reason) => ({ status: 'rejected', reason });
 const resolveHandler = (value) => ({ status: 'fulfilled', value });
 ​
 Promise.myAllSettled = (promises) => {
   const newPromises = promises.map((p) => Promise.resolve(p).then(resolveHandler, rejectHandler));
   return Promise.all(newPromises);
 };

race()

  • Promise.all 类似,但只等待第一个完成的 promise 并获取其结果或错误信息
 const promise1 = new Promise((_, reject) => setTimeout(() => reject('error message'), 1500));
 const promise2 = new Promise((resolve) => setTimeout(() => resolve(2), 500));
 const promise3 = new Promise((resolve) => setTimeout(() => resolve(3), 1500));
 ​
 Promise.race([promise1, promise2, promise3]).then(console.log); // 2
  • 当其中一个 Promise 状态变成 fulfilledrejectedPromise.race 返回的新 Promise 也完成,其状态取决于最快完成的 Promise
  • 当一个 Promise 完成后,后面所有的 Promise 都会被忽略,但是操作仍继续执行

any()

  • any 方法是 ES2021 中新增的方法,与 Promise.race 类似

race 方法的区别

  • Promise.any 只等待第一个 fulfilledpromise,并将其返回,然后决定新返回 Promise 的状态
 const promise1 = new Promise((_, reject) => setTimeout(() => reject('error message'), 500));
 const promise2 = new Promise((_, reject) => setTimeout(() => reject('error message2'), 1000));
 const promise3 = new Promise((resolve) => setTimeout(() => resolve(3), 1500));
 ​
 // 这里会等待1.5秒后输出3,reject的promise是忽略的
 Promise.any([promise1, promise2, promise3]).then(console.log); // 3
  • 如果所有 promise 都是 rejected 状态,那么 any 方法会返回带有 AggregateError 对象的 promise,其 errors 属性中存储着所有错误信息
 const promise1 = new Promise((_, reject) => setTimeout(() => reject('error message'), 500));
 const promise2 = new Promise((_, reject) => setTimeout(() => reject('error message2'), 1000));
 const promise3 = new Promise((_, reject) => setTimeout(() => reject(3), 1500));
 ​
 Promise.any([promise1, promise2, promise3]).catch(console.log);

JavaScript高级--Promise①(概念与使用)