彻底掌握 Promise

彻底掌握 Promise一 Promise 出现的原因在 Promise 出现以前 我们处理一个异步网络请求 大概是这样 请求代表一个异步网络调用


其他相关传送门

  1. Promise异步操作详解
  2. Promise详细用法
  3. 手写一个Promise
  4. 细说 async/await

一、Promise 出现的原因

在 Promise 出现以前,我们处理一个异步网络请求,大概是这样:

// 请求 代表 一个异步网络调用。 // 请求结果 代表网络请求的响应。 请求1(function(请求结果1){ 
    处理请求结果1 }) 
请求1(function(请求结果1){ 
    请求2(function(请求结果2){ 
    处理请求结果2 }) }) 
请求1(function(请求结果1){ 
    请求2(function(请求结果2){ 
    请求3(function(请求结果3){ 
    请求4(function(请求结果4){ 
    请求5(function(请求结果5){ 
    请求6(function(请求结果6){ 
    ... }) }) }) }) }) }) 

这回傻眼了。。。 臭名昭著的 回调地狱 现身了。

更糟糕的是,我们基本上还要对每次请求的结果进行一些处理,代码会更加臃肿,在一个团队中,代码 review 以及后续的维护将会是一个很痛苦的过程。

回调地狱带来的负面作用有以下几点:

  • 代码臃肿
  • 可读性差
  • 耦合度过高,可维护性差
  • 代码复用性差
  • 容易滋生 bug
  • 只能在回调里处理异常

出现了问题,自然就会有人去想办法。这时,就有人思考了,能不能用一种更加友好的代码组织方式,解决异步嵌套的问题。

let 请求结果1 = 请求1(); let 请求结果2 = 请求2(请求结果1); let 请求结果3 = 请求3(请求结果2); let 请求结果4 = 请求2(请求结果3); let 请求结果5 = 请求3(请求结果4); 

类似上面这种同步的写法。 于是 Promise 规范诞生了,并且在业界有了很多实现来解决回调地狱的痛点。比如业界著名的 Q 和 bluebird,bluebird 甚至号称运行最快的类库。


二、什么是 Promise?

Promise 是异步编程的一种解决方案:

  • 从语法上讲,Promise 是一个 ES6 的原生对象,从它可以获取异步操作的消息;
  • 从本意上讲,它是承诺,承诺它过一段时间会给你一个结果。
  • Promise 对象代表了未来将要发生的事件,用来传递异步操作的消息。

Promise 有三种状态:

  • pending (等待态)
  • fulfiled (成功态)
  • rejected (失败态)

.

  • Promise 状态的转变是不可逆且只能发生一次
  • 也就是说,一个 Promise 不能从 fulfiled 状态变回 pending 状态,也不能从 rejected 状态变为 pending 或者 fulfiled 状态
  • 一旦 Promise 从 pending 状态变为 fulfiled 或 rejected ,它就永远不会再改变
  • 创造 promise 实例后,它会立即执行

在这里插入图片描述

基本过程:

  1. 初始化 Promise 状态(pending);
  2. 立即执行 Promise 中传入的 fn 函数,将Promise 内部 resolve、reject 函数作为参数传递给 fn ,按事件机制时机处理;
  3. 执行 then(…) 注册回调处理数组(then 方法可被同一个 promise 调用多次);
  4. Promise里的关键是要保证,then方法传入的参数 onFulfilled 和 onRejected,必须在then方法被调用的那一轮事件循环之后的新执行栈中执行;

promise是用来解决两个问题的:

  • 回调地狱,代码难以维护, 常常第一个的函数的输出是第二个函数的输入这种现象
  • promise可以支持多个并发的请求,获取并发请求中的数据
  • 这个promise可以解决异步的问题,本身不能说promise是异步的

Promise最大的好处是在异步执行的流程中,把执行代码和处理结果的代码清晰地分离了


三、Promise 的特点

  1. 对象的状态不受外界影响
    Promise对象代表一个异步操作,有三种状态:
    pending(进行中)、fulfilled(已成功)和 rejected(已失败)
    只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
  2. 一旦状态改变,就不会再变,任何时候都可以得到这个结果

四、Promise 的优缺点

优点:

  1. 有了 Promise 对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数(回调地狱)。
  2. 此外,Promise 对象提供统一的接口,使得控制异步操作更加容易。

缺点:

  1. 无法取消 Promise,一旦新建它就会立即执行,无法中途取消。
  2. 如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部。
  3. 当处于 Pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

五、Promise 是同步还是异步?

  1. Promise 本身是同步的 ,是用来管理异步编程的。
  2. Promise 的回调thencatch是异步 ,解决异步回调的问题。

六、Promise 的用法

Promise 的构造函数接收一个参数:函数,并且这个函数需要传入两个参数:

  • resolve :异步操作执行成功后的回调函数(把状态设置为成功,执行then方法)
  • reject:异步操作执行失败后的回调函数(把状态设置为失败,执行catch方法)
let p = new Promise((resolve, reject) => { 
    var num = Math.ceil(Math.random() * 10); //生成1-10的随机数 if (num <= 5) { 
    resolve(num); } else { 
    reject('数字太大了'); } }) 

Promise 的实例方法

1. then 的用法

then() 方法返回一个 新的Promise对象。

then 方法最多传入两个参数:

  1. 第一个对应 resolve 的回调
  2. 第二个对应 reject 的回调。

所以我们能够分别拿到他们传过来的数据:

let p = new Promise((resolve, reject) => { 
    var num = Math.ceil(Math.random() * 10); //生成1-10的随机数 if (num <= 5) { 
    resolve(num); } else { 
    reject('数字太大了'); } }) p.then((data) => { 
    console.log('resolve:' + data); // 处理resolve回调 }, (err) => { 
    console.log('reject:' + err); // 处理reject回调 }) 

多次运行这段代码,你会随机得到下面两种结果:
resolve:5reject:数字太大了
.

返回了一个值,或没有返回任何值(undefined):
那么 then 返回的 Promise 将会成为接受状态,并且将返回的值作为接受状态的回调函数的参数值。即相当于返回 Promise.resolve(返回的值/undefined)。

Promise.resolve(33).then((data)=>{ 
    console.log(data) // 33 return 100; }).then((val)=>{ 
    console.log(val) ; // 100 }).then((res)=>{ 
    console.log(res); // undefined }) 

then的链式调用:

所以,从表面上看,Promise只是能够简化层层回调的写法,而实质上,Promise的精髓是“状态”,用维护状态、传递状态的方式来使得回调函数能够及时调用,它比传递callback函数要简单、灵活的多。所以使用Promise的正确场景是这样的:

request('test1.html', data) .then(function (data1) { 
    console.log('第一次请求成功, 这是返回的数据:', data1); return request('test2.html', data1); }) .then(function (data2) { 
    console.log('第二次请求成功, 这是返回的数据:', data2); return request('test3.html', data2); }) .then(function (data3) { 
    console.log('第三次请求成功, 这是返回的数据:', data3); }) .catch(function (error) { 
    console.log('sorry, 请求失败了, 这是失败信息:', error); }); 

Promise对象的then方法返回一个新的Promise对象,因此可以通过链式调用then方法。

这两个参数的返回值可以是以下三种情况中的一种:

  1. return 一个同步的值 ,或者 undefined(当没有返回一个有效值时,默认返回undefined),then方法将返回一个resolved状态的Promise对象,Promise对象的值就是这个返回值。
  2. return 另一个 Promise,then方法将根据这个Promise的状态和值创建一个新的Promise对象返回。
  3. throw 一个同步异常,then方法将返回一个rejected状态的Promise, 值是该异常。
var p = new Promise(function (resolve, reject) { 
    resolve(1); }); p.then(function (value) { 
    console.log(value); // 1 return value * 2; // return一个同步的值(resolved状态) }).then(function (value) { 
    console.log(value); // 2 }).then(function (value) { 
    console.log(value); // 上面的then没有return,因此默认是return undefined return Promise.resolve(3); }).then(function (value) { 
    console.log(value); // 接收到resolve(3) return Promise.reject(4); }).then(function (value) { 
    console.log('resolve: ' + value); }, function (err) { 
    console.log('reject: ' + err); // 接收到reject(4) }) 

控制台输出:

1 2 undefined 3 "reject: 4" 

.

2. catch 的用法

用于处理Promise失败后的回调函数,也返回一个新的Promise对象。

它主要用于捕获异步操作过程中发生的错误。其实它和 then 的第二个参数一样,用来指定 reject 的回调。 用法是这样:

let p = new Promise((resolve, reject) => { 
    var num = Math.ceil(Math.random() * 10); //生成1-10的随机数 if (num <= 5) { 
    resolve(num); } else { 
    reject('数字太大了'); } }) p.then((data) => { 
    console.log('resolve:' + data); }) .catch((err) => { 
    console.log('reject:' + err); }) 

效果和写在then的第二个参数里面一样。

不过它还有另外一个作用:在执行 resolve 的回调(也就是上面 then 中的第一个参数)时,如果抛出异常了(代码出错了),那么并不会报错卡死 js,而是会进到这个 catch 方法中。

catch 既能处理 reject 回调,也能捕捉错误

请看下面的代码:

p.then((data) => { 
    console.log('resolved',data); console.log(somedata); //此处的somedata未定义 }) .catch((err) => { 
    console.log('rejected',err); }); 

在resolve的回调中,我们console.log(somedata);而somedata这个变量是没有被定义的。
如果我们不用Promise,代码运行到这里就直接在控制台报错了,不往下运行了。
但是在这里,会得到这样的结果:
在这里插入图片描述
也就是说进到catch方法里面去了,而且把错误原因传到了reason参数中。
即便是有错误的代码也不会报错了,这与我们的try/catch语句有相同的功能

.

3. finally 的用法

finally()方法用于指定不管 Promise 对象最后状态如何,都会执行的操作

它常常用于清理资源或执行一些不论成功失败都需要进行的操作。

promise .then(result => { 
   ···}) .catch(error => { 
   ···}) .finally(() => { 
   ···}); 

Promise 的静态方法

1. Promise.all 的用法

这个方法接受一个Promise对象的数组作为参数

只有当数组中的所有Promise对象都成功完成时,它才会返回一个新的成功的Promise对象,其结果是一个由每个Promise对象的返回值组成的数组。

如果有任何一个Promise对象失败,all()方法将立即返回一个失败的Promise对象。

多个 Promise 任务同时执行。 如果全部成功执行,则以数组的方式返回所有 Promise 任务的执行结果。 如果有一个Promise 任务 rejected,则只返回 rejected 任务的结果。

let Promise1 = new Promise(function(resolve, reject){ 
   }) let Promise2 = new Promise(function(resolve, reject){ 
   }) let Promise3 = new Promise(function(resolve, reject){ 
   }) let p = Promise.all([Promise1, Promise2, Promise3]) p.then(funciton(){ 
    // 三个都成功,则成功  }, function(){ 
    // 只要有失败,则失败 }) 

三个都成功,则成功
只要有失败,则失败

let p1 = new Promise((resolve, reject) => { 
    resolve('成功了'); }) let p2 = new Promise((resolve, reject) => { 
    resolve('success'); }) let p3 = Promse.reject('失败'); Promise.all([p1, p2]).then((result) => { 
    console.log(result) //['成功了', 'success'] }).catch((error) => { 
    console.log(error) }) Promise.all([p1,p3,p2]).then((result) => { 
    console.log(result) }).catch((error) => { 
    console.log(error) // 失败了,打出 '失败' }) 

Promse.all 在处理多个异步处理时非常有用,比如说一个页面上需要等两个或多个 ajax 的数据回来以后才正常显示,在此之前只显示 loading 图标。

showLoading(); // 显示loading let p1 = new Promise((resolve, reject) => { 
    resolve('请求1成功'); }) let p2 = new Promise((resolve, reject) => { 
    resolve('请求2成功'); }) Promise.all([p1, p2]).then(() => { 
    hideLoading(); // 消失loading }) 

需要特别注意的是,Promise.all获得的成功结果的数组里面的数据顺序和Promise.all接收到的数组顺序是一致的,即p1的结果在前,即便p1的结果获取的比p2要晚。这带来了一个绝大的好处:在前端开发请求数据的过程中,偶尔会遇到发送多个请求并根据请求顺序获取和使用数据的场景,使用Promise.all毫无疑问可以解决这个问题。

.

2. Promise.race 的用法

顾名思义,Promse.race 就是赛跑的意思,意思就是说,
Promise.race([p1, p2, p3])里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。

这个方法也接受一个Promise对象的数组作为参数

与all()不同的是,race()方法会在数组中的任何一个Promise对象最先完成(无论是成功还是失败)时,就返回一个新的Promise对象,其结果就是那个最先完成的Promise对象的结果。

这里强调下,其它的promise实例仍然会继续运行,只不过其状态和结果不会归于最终的结果。

let p1 = new Promise((resolve, reject) => { 
    setTimeout(() => { 
    resolve('success') },1000) }) let p2 = new Promise((resolve, reject) => { 
    setTimeout(() => { 
    reject('failed') }, 500) }) Promise.race([p1, p2]).then((result) => { 
    console.log(result) }).catch((error) => { 
    console.log(error) // 打开的是 'failed' }) 

race的使用场景:比如我们可以用race给某个异步请求设置超时时间,并且在超时后执行相应的操作,代码如下:

//请求某个图片资源 function requestImg(){ 
    var p = new Promise((resolve, reject) => { 
    var img = new Image(); img.onload = function(){ 
    resolve(img); } img.src = '图片的路径'; }); return p; } //延时函数,用于给请求计时 function timeout(){ 
    var p = new Promise((resolve, reject) => { 
    setTimeout(() => { 
    reject('图片请求超时'); }, 5000); }); return p; } Promise.race([requestImg(), timeout()]).then((data) =>{ 
    console.log(data); }).catch((err) => { 
    console.log(err); }); 

requestImg函数会异步请求一张图片,我把地址写为"图片的路径",所以肯定是无法成功请求到的。timeout函数是一个延时5秒的异步操作。我们把这两个返回Promise对象的函数放进race,于是他俩就会赛跑,如果5秒之内图片请求成功了,那么遍进入then方法,执行正常的流程。如果5秒钟图片还未成功返回,那么timeout就跑赢了,则进入catch,报出“图片请求超时”的信息。

.

3. Promise.allSettled 的用法

  • 接收一个 Promise 对象数组作为参数
  • 和 Promise.all() 相似,它等待所有 Promise 都达到稳定(Settled)状态(即无论是 fulfilled 还是 rejected )后才会确定
    返回一个新的 Promise 对象,这个新的 Promise 在数组中的所有 Promise 都已经确定状态后才会确定
  • 返回的 Promise 会解析为一个包含每个 Promise 结果的数组,每个结果都是一个包含 status 属性和 value 或 reason 属性的对象。
    一旦所有Promise都确定了,返回的 Promise 会变成 fulfilled ,并且结果是一个数组,包含了每个输入 Promise 的结果描述对象,这些对象具有status( fulfilled 或 rejected )和对应的 value 或 reason 属性。
  • 不会因为数组中的某个 Promise 被拒绝而立即返回,它会等待所有的 Promise 完成
  • Promise.all() 更关注所有 Promise 是否都成功完成,它适用于需要所有任务成功完成才能继续下一步场景。
    而 Promise.allSettled() 则允许你观察一组异步操作的所有结果,无论成功与否,这对于获取并处理所有任务的最终状态非常有用。
const promise1 = Promise.resolve(3); const promise2 = 42; // promise2 是一个非 Promise 值,它会被隐式地转换为一个已解析的 Promise。 const promise3 = new Promise((resolve, reject) => { 
    setTimeout(reject, 100, 'foo'); }); const promise4 = new Promise((resolve, reject) => { 
    setTimeout(resolve, 50, 'bar'); }); const promises = [promise1, promise2, promise3, promise4]; Promise.allSettled(promises). then((results) => results.forEach((result) => console.log(result.status))); // 输出: // "fulfilled" // "fulfilled" // "rejected" // "fulfilled" 

.

4. Promise.resolve 的用法

var p1 = Promise.resolve(1); var p2 = Promise.resolve(p1); var p3 = new Promise(function(resolve, reject){ 
    resolve(1); }); var p4 = new Promise(function(resolve, reject){ 
    resolve(p1); }); console.log(p1 === p2); console.log(p1 === p3); console.log(p1 === p4); console.log(p3 === p4); p4.then(function(value){ 
    console.log('p4=' + value); }); p2.then(function(value){ 
    console.log('p2=' + value); }) p1.then(function(value){ 
    console.log('p1=' + value); }) 

控制台输出:

true false false false p2=1 p1=1 p4=1 

Promise.resolve(...) 可以接收一个 值 或者是一个 Promise对象 作为参数。

但通过new的方式创建的Promise对象都是一个新的对象,因此后面的三个比较结果都是false。

.

无论是 then 还是 catch ,只要不报错,都返回的是 resolved 状态的 promise
报错则返回的是 rejected 状态的 promise

Promise.resolve().then(() => { 
    console.log(1); // 不报错,返回的是resolved状态的promise,触发then回调 }).catch(() => { 
    console.log(2); // 不触发 }).then(() => { 
    console.log(3); // 触发 }) // 1 // 3 
Promise.resolve().then(() => { 
    console.log(1); throw new Error('error'); // 报错,返回的是rejected状态的promise,触发catch回调 }).catch(() => { 
    console.log(2); // 不报错,返回的是resolved状态的promise,触发then回调 }).then(() => { 
    console.log(3); // 触发 }) // 1 // 2 // 3 

5. Promise.reject 的用法

用于创建一个已拒绝的 Promise。

接收一个原因(reason)作为参数,并返回一个已拒绝为该原因的 Promise 对象。

const promise = Promise.reject('error'); promise.catch((reason) => { 
    console.log(reason); // 输出 "error" }); 

Promise.reject('error') 创建了一个已拒绝的 Promise,原因是字符串 error

然后,我们使用 catch() 方法来处理这个已拒绝的 Promise。

catch() 方法接收一个回调函数作为参数,这个回调函数在 Promise 被拒绝时被调用,并接收 Promise 的原因作为参数。在这个例子中,回调函数输出了 Promise 的原因,即字符串 error


七、异常捕获的三种方法

  • then函数中传递第二个函数作为第二个参数,用来执行reject函数
  • catch函数
  • finally ,不论成败,resolve/reject,最后都会执行

八、相较于Promise,async/await有何优势?

  1. 同步化代码的阅读体验(Promise 虽然摆脱了回调地狱,但 then 链式调⽤的阅读负担还是存在的)
  2. 和同步代码更一致的错误处理方式( async/await 可以⽤成熟的 try/catch 做处理,比 Promise 的错误捕获更简洁直观)
  3. 调试时的阅读性, 也相对更友好
今天的文章 彻底掌握 Promise分享到此就结束了,感谢您的阅读。
编程小号
上一篇 2024-12-11 09:46
下一篇 2024-12-11 09:40

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/bian-cheng-ji-chu/83685.html