对Promise的理解

一、Promise的理解与使用
1、概念:

​ Promise是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。

通俗讲,Promise是一个许诺、承诺,是对未来事情的承诺,承诺不一定能完成,但是无论是否能完成都会有一个结果。

Pending 正在做。。。
Resolved 完成这个承诺
Rejected 这个承诺没有完成,失败了
​ Promise 用来预定一个不一定能完成的任务,要么成功,要么失败

​ 在具体的程序中具体的体现,通常用来封装一个异步任务,提供承诺结果

Promise 是异步编程的一种解决方案,主要用来解决回调地狱的问题,可以有效的减少回调嵌套。真正解决需要配合async/await

2、特点:

​ (1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:Pending(进行中)、Resolved(已完成,又称Fulfilled)和Rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。

​ (2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从Pending变为Resolved和从Pending变为Rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。就算改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。

3、缺点:

​ (1)无法取消Promise,一旦新建它就会立即执行,无法中途取消。和一般的对象不一样,无需调用。

​ (2)如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。

​ (3)当处于Pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)

1、Promise是什么?
1-理解
抽象表达:
​ 1) Promise 是一门新的技术(ES6 规范)

​ 2)Promise 是 JS 中进行异步编程的新解决方案 备注:旧方案是单纯使用回调函数

具体表达:
从语法上来说: Promise 是一个构造函数

从功能上来说: promise 对象用来封装一个异步操作并可以获取其成功/ 失败的结果值

2-promise 的属性
a) promise 的状态
实例对象中的一个属性 『PromiseState』

pending 等待(进行中),未决定的
resolved / fullfilled 成功,调用resoleve
rejected 失败,调用reject
b) promise 的状态改变
pending 变为 resolved

pending 变为 rejected

说明: 只有这 2 种, 且一个 promise 对象只能改变一次 无论变为成功还是失败, 都会有一个结果数据 成功的结果数据一般称为 value, 失败的结果数据一般称为 reason

3-promise的基本流程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pc8ynJl4-1648954802665)(A_Promise系统学习笔记中的图片/Promise系统学习_promise工作流程.png)]

4-promise的基本使用
1. promise的两个参数:resolve、reject
// resolve 解决  函数类型的数据,对象返回成功时使用
// reject  拒绝  函数类型的数据,对象返回失败时使用
const p = new Promise((resolve, reject) => {
    setTimeout(() => {
        //30%  1-100  1 2 30
        //获取从1 - 100的一个随机数
        let n = rand(1, 100);
        //判断
        if(n <= 30){
            //传值,传到then中
            resolve(n); // 将 promise 对象的状态设置为 『成功』
        }else{
            reject(n); // 将 promise 对象的状态设置为 『失败』
        }
    }, 1000);
});
//then需要接收两个参数,两个参数都是函数类型,第一个函数是对象成功时的回调,第二是失败时的回调
p.then((value) => {  //接收上面异步的值
                alert('恭喜恭喜, 奖品为 10万 RMB 劳斯莱斯优惠券, 您的中奖数字为 ' + value);
            }, (reason) => {
                alert('再接再厉, 您的号码为 ' + reason);
            });

        });

1
2.使用 promise 封装 ajax 异步请求
<script >
  /*
  可复用的发 ajax 请求的函数: xhr + promise
  */
  function promiseAjax(url) {
    return new Promise((resolve, reject) => {
      const xhr = new XMLHttpRequest()
      xhr.onreadystatechange = () => {
        if (xhr.readyState !== 4) return
        const {
          status,
          response
        } = xhr
        // 请求成功, 调用 resolve(value)
        if (status >= 200 && status < 300) {
          resolve(JSON.parse(response))
        } else { // 请求失败, 调用 reject(reason)
          reject(new Error('请求失败: status: ' + status))
        }
      }
      xhr.open("GET", url)
      xhr.send()
    })
  }
promiseAjax('https://api.apiopen.top2/getJoke?page=1&count=2&type=vid
    eo ')
    .then(
      data => {
        console.log('显示成功数据', data)
      },
      error => {
        alert(error.message)
      }
    ) </script>


3.fs模块使用Promise
const fs = require('fs');

//回调函数 形式,读取content.txt文件    ----------------------------------------------------
 fs.readFile('./resource/content.txt', (err, data) => {
     // 如果出错 则抛出错误
     if(err)  throw err;
     //输出文件内容
     console.log(data.toString());
 });

//Promise 形式-----------------------------------------------------------
/**
 * 封装一个函数 mineReadFile 读取文件内容
 * 参数:  path  文件路径
 * 返回:  promise 对象
 */
function mineReadFile(path){
    return new Promise((resolve, reject) => {
        //读取文件
        require('fs').readFile(path, (err, data) =>{
            //判断
            if(err) reject(err);
            //成功
            resolve(data);
        });
    });
}

mineReadFile('./resource/content.txt')
.then(value=>{
    //输出文件内容
    console.log(value.toString());
}, reason=>{
    console.log(reason);
});


4.异常穿透
可以在每个then()的第二个回调函数中进行err处理,也可以利用异常穿透特性,到最后用catch去承接统一处理,两者一起用时,前者会生效(因为err已经将其处理,就不会再往下穿透)而走不到后面的catch

在每个.then()中我可以将数据再次传出给下一个then()

mineReadFile('./11.txt').then(result=>{
  console.log(result.toString())
  return result
},err=>console.log(err))
.then(data=>console.log(data,"2222222"))
.catch(err=>console.log("这是catch的"))


5.util.promisify方法
可以将函数直接变成promise的封装方式,不用再去手动封装

//引入 util 模块
const util = require('util');
//引入 fs 模块
const fs = require('fs');
//返回一个新的函数
let mineReadFile = util.promisify(fs.readFile);

mineReadFile('./resource/content.txt').then(value => {
  console.log(value.toString());
});

2、为什么要用Promise?
1-指定回调函数的方式更加灵活
旧的: 必须在启动异步任务前指定
promise: 启动异步任务 => 返回promie对象 => 给promise对象绑定回调函 数(甚至可以在异步任务结束后指定/多个)
2-支持链式调用, 可以解决回调地狱问题
1、什么是回调地狱
回调函数嵌套调用, 外部回调函数异步执行的结果是嵌套的回调执行的条件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2kzq17S8-1648954802666)(A_Promise系统学习笔记中的图片/Promise系统学习_回调地狱.jpg)]

2、回调地狱的缺点?
不便于阅读 不便于异常处理

3、解决方案?
promise 链式调用,

用来解决回调地狱问题,但是只是简单的改变格式,并没有彻底解决上面的问题真正要解决上述问题,一定要利用promise再加上await和async关键字实现异步传同步

4、终极解决方案?
promise +async/await

3、Promise中的常用 API 概述
此处列举几个最常用的API的概述,如果想看详细描述的可以继续往下看下方的 Promise方法的具体使用 描述

1- Promise 构造函数: Promise (excutor) {}
(1) executor 函数: 执行器 (resolve, reject) => {}

(2) resolve 函数: 内部定义成功时我们调用的函数 value => {}

(3) reject 函数: 内部定义失败时我们调用的函数 reason => {}

说明: executor 会在 Promise 内部立即同步调用,异步操作在执行器中执行,换话说Promise支持同步也支持异步操作

2-Promise.prototype.then 方法: (onResolved, onRejected) => {}
(1) onResolved 函数: 成功的回调函数 (value) => {}

(2) onRejected 函数: 失败的回调函数 (reason) => {}

说明: 指定用于得到成功 value 的成功回调和用于得到失败 reason 的失败回调 返回一个新的 promise 对象

3-Promise.prototype.catch 方法: (onRejected) => {}
(1) onRejected 函数: 失败的回调函数 (reason) => {}

说明: then()的语法糖, 相当于: then(undefined, onRejected)

(2) 异常穿透使用:当运行到最后,没被处理的所有异常错误都会进入这个方法的回调函数中

  let p = new Promise((resolve, reject) => {
           // ** 同步调用
           reject('error');
       });
       //执行 catch 方法
       p.catch(reason => {
           console.log(reason);
       });

4-Promise.resolve 方法: (value) => {}
(1) value: 成功的数据或 promise 对象

说明: 返回一个成功/失败的 promise 对象,直接改变promise状态

let p1 = Promise.resolve(521);
       //如果传入的参数为 非Promise类型的对象, 则返回的结果为成功promise对象
       //如果传入的参数为 Promise 对象, 则参数的结果决定了 resolve 的结果
       let p2 = Promise.resolve(new Promise((resolve, reject) => {
           // resolve('OK');
           reject('Error');
       }));
       // console.log(p2);
       p2.catch(reason => {
           console.log(reason);
       })

5-Promise.reject 方法: (reason) => {}
(1) reason: 失败的原因

说明: 返回一个失败的 promise 对象,直接改变promise状态,代码示例同上

6-Promise.all 方法: (promises) => {}
promises: 包含 n 个 promise 的数组

说明: 返回一个新的 promise, 只有所有的 promise 都成功才成功, 只要有一 个失败了就直接失败

 let p1 = new Promise((resolve, reject) => { resolve('成功');  })
    let p2 = Promise.reject('错误错误错误');
    let p3 = Promise.resolve('也是成功')
    const result = Promise.all([p1, p2, p3]);
 console.log(result);

7-Promise.race 方法: (promises) => {}
(1) promises: 包含 n 个 promise 的数组

说明: 返回一个新的 promise, 第一个完成的 promise 的结果状态就是最终的结果状态,

如p1延时,开启了异步,内部正常是同步进行,所以p2>p3>p1,结果是P2

 let p1 = new Promise((resolve, reject) => {
     setTimeout(() => {
       resolve('OK');
     }, 1000);
   })
   let p2 = Promise.resolve('Success');
   let p3 = Promise.resolve('Oh Yeah');
   //调用
   const result = Promise.race([p1, p2, p3]);
   console.log(result);

4、Promise的几个关键问题
1-如何改变 promise 的状态?
(1) resolve(value): 如果当前是 pending 就会变为 resolved

(2) reject(reason): 如果当前是 pending 就会变为 rejected

(3) 抛出异常throw: 如果当前是 pending 就会变为 rejected

2-一个 promise 指定多个成功/失败回调函数, 都会调用吗?
当 promise 改变为对应状态时都会调用,改变状态后,多个回调函数都会调用,并不会自动停止

let p = new Promise((resolve, reject) => {  resolve('OK');});
  ///指定回调 - 1
  p.then(value => {  console.log(value); });
  //指定回调 - 2
  p.then(value => { alert(value);});

3- 改变 promise 状态和指定回调函数谁先谁后?
(1) 都有可能, 正常情况下是先指定回调再改变状态, 但也可以先改状态再指定回调

​ 先指定回调再改变状态(异步):先指定回调–> 再改变状态 -->改变状态后才进入异步队列执行回调函数

​ 先改状态再指定回调(同步):改变状态 -->指定回调 并马上执行回调

(2) 如何先改状态再指定回调? -->注意:指定并不是执行

​ ① 在执行器中直接调用 resolve()/reject() -->即,不使用定时器等方法,执行器内直接同步操作

​ ② 延迟更长时间才调用 then() -->即,在.then()这个方法外再包一层例如延时器这种方法

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

​ ① 如果先指定的回调, 那当状态发生改变时, 回调函数就会调用, 得到数据

​ ② 如果先改变的状态, 那当指定回调时, 回调函数就会调用, 得到数据

let p = new Promise((resolve, reject) => {
//异步写法,这样写会先指定回调,再改变状态
setTimeout(() => {resolve('OK'); }, 1000);
//这是同步写法,这样写会先改变状态,再指定回调
resolve('OK'); 
});
p.then(value => {console.log(value);}, reason => {})

(4) 个人理解–结合源码

​ 源码中,promise的状态是通过一个默认为padding的变量进行判断,所以当你resolve/reject延时(异步导致当then加载时,状态还未修改)后,这时直接进行p.then()会发现,目前状态还是进行中,所以只是这样导致只有同步操作才能成功.

​ 所以promise将传入的回调函数拷贝到promise对象实例上,然后在resolve/reject的执行过程中再进行调用,达到异步的目的

​ 具体代码实现看下方自定义promise

4-promise.then()返回的新 promise 的结果状态由什么决定?
(1) 简单表达: 由 then()指定的回调函数执行的结果决定

(2) 详细表达:

​ ① 如果抛出异常, 新 promise 变为 rejected, reason 为抛出的异常

​ ② 如果返回的是非 promise 的任意值, 新 promise 变为 resolved, value 为返回的值

​ ③ 如果返回的是另一个新 promise, 此 promise 的结果就会成为新 promise 的结果

let p = new Promise((resolve, reject) => {
resolve('ok');
});
//执行 then 方法
let result = p.then(value => {
console.log(value);
// 1. 抛出错误 ,变为 rejected
throw '出了问题';
// 2. 返回结果是非 Promise 类型的对象,新 promise 变为 resolved
return 521;
// 3. 返回结果是 Promise 对象,此 promise 的结果就会成为新 promise 的结果
return new Promise((resolve, reject) => {
  // resolve('success');
  reject('error');
});
}, reason => {
console.warn(reason);
});


Ⅴ- promise 如何串连多个操作任务?
(1) promise 的 then()返回一个新的 promise, 可以开成 then()的链式调用

(2) 通过 then 的链式调用串连多个同步/异步任务,这样就能用then()将多个同步或异步操作串联成一个同步队列

<script>
let p = new Promise((resolve, reject) => { setTimeout(() => {resolve('OK'); }, 1000); });
p.then(value => {return new Promise((resolve, reject) => { resolve("success"); });})
.then(value => {console.log(value);})
.then(value => { console.log(value);})
</script>

Ⅵ-promise 异常传透?
当使用 promise 的 then 链式调用时, 可以在最后指定失败的回调
前面任何操作出了异常, 都会传到最后失败的回调中处理
getJSON('./hong.json')
   .then(function(posts) { throw new Error('抛出异常') })
    .then(res=>console.log(res),e=>console.log('被then的错误回调捕获',e) )
   .catch(function(error) {
         // 处理 getJSON 和 前一个回调函数运行时发生的错误
         console.log('错误捕获: ', error);
    });
//执行结果: 被then的错误回调捕获 Error: 抛出异常

/******************** 利用异常穿透 ****************************************/
getJSON('./hong.json')
   .then(function(posts) { throw new Error('抛出异常') })
    .then(res=>console.log(res) ) //此处差异,不指定 reject 回调,利用异常穿透传到最后
   .catch(function(error) {
         console.log('错误捕获: ', error);
    });
//执行结果:  错误捕获:  Error: 抛出异常


注:可以在每个then()的第二个回调函数中进行err处理,也可以利用异常穿透特性,到最后用catch去承接统一处理,两者一起用时,前者会生效(因为err已经将其处理,就不会再往下穿透)而走不到后面的catch[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MXhmIXkd-1648954802667)(A_Promise系统学习笔记中的图片/image-20210927105504988.png)]

Ⅶ- 中断 promise 链?
在关键问题2中,可以得知,当promise状态改变时,他的链式调用都会生效,那如果我们有这个一个实际需求:我们有5个then(),但其中有条件判断,如当我符合或者不符合第三个then条件时,要直接中断链式调用,不再走下面的then,该如何?

(1) 当使用 promise 的 then 链式调用时, 在中间中断, 不再调用后面的回调函数

(2) 办法: 在回调函数中返回一个 pendding 状态的promise 对象

<script>
let p = new Promise((resolve, reject) => {setTimeout(() => { resolve('OK');}, 1000);});
p.then(value => {return new Promise(() => {});})//有且只有这一个方式
.then(value => { console.log(222);})
.then(value => { console.log(333);})
.catch(reason => {console.warn(reason);});
</script>

5、 Promise的实际应用
举两个栗子

1 - 加载图片
我们可以将图片的加载写成一个Promise,一旦加载完成,Promise的状态就发生变化。

const preloadImage = function (path) {
 return new Promise(function (resolve, reject) {
   const image = new Image();
   image.onload  = resolve;
   image.onerror = reject;
   image.src = path;
 });
};

2 - Generator 函数与 Promise 的结合
使用 Generator 函数管理流程,遇到异步操作的时候,通常返回一个Promise对象。

function getFoo () {
 return new Promise(function (resolve, reject){
   resolve('foo');
 });
}

const g = function* () {
 try {
   const foo = yield getFoo();
   console.log(foo);
 } catch (e) {
   console.log(e);
 }
};

function run (generator) {
 const it = generator();

 function go(result) {
   if (result.done) return result.value;

   return result.value.then(function (value) {
     return go(it.next(value));
   }, function (error) {
     return go(it.throw(error));
   });
 }

 go(it.next());
}

run(g);


上面代码的 Generator 函数g之中,有一个异步操作getFoo,它返回的就是一个Promise对象。函数run用来处理这个Promise对象,并调用下一个next方法。

二、Promise API 用法详解
ES6 规定,Promise对象是一个构造函数,用来生成Promise实例。

此部分是对于 Promise API 用法的详解 ,尽量详细地列举其常见用法,所以篇幅较长

1 - 基本用法
① 举个创造 Promise 实例的栗子
下面代码创造了一个Promise实例。

const promise = new Promise(function(resolve, reject) {
if (/* 异步操作成功 */)  resolve(value); //将该 Promise 修改为成功且返回
else  reject(error); //将该 Promise 修改为失败且返回
});

Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。

resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

② 使用 [ then ] 方法分别指定 成功/失败 的回调
Promise实例生成以后,可以用 [ then() ] 方法分别指定resolved状态和rejected状态的回调函数。

promise.then(function(value) {
// 当promise状态返回为resolve 时会执行的回调函数
}, function(error) {
// 当promise状态返回为rejected 时会执行的回调函数
});

[ then ] 方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为resolved时调用,第二个回调函数是Promise对象的状态变为rejected时调用。其中,第二个函数是可选的,不一定要提供。这两个函数都接受Promise对象传出的值作为参数。

③ 举个 Promise 对象的简单栗子
下面是一个Promise对象的简单例子。

setTimeout的第三个参数是给第一个函数的参数,而且是先于第一个参数(即回调函数)执行的

function timeout(ms) { //声明一个方法, 传入的 参数ms 为延时器时间
return new Promise((resolve, reject) => {
  //这行代码实际效果: 当 [ms] 毫秒后 执行 resolve('努力学习的汪')
  setTimeout(resolve, ms, '努力学习的汪'); 
});
}

timeout(1000).then((value) => {  console.log(value) });
//打印结果 : 努力学习的汪

上面代码中,timeout方法返回一个Promise实例,表示一段时间以后才会发生的结果。过了指定的时间(ms参数)以后,Promise实例的状态变为resolved,就会触发then方法绑定的回调函数。

④ Promise 新建后就会立即执行
let promise = new Promise(function(resolve, reject) {
console.log('Promise');
resolve();
});

promise.then(function() {
console.log('resolved.');
});

console.log('Hi!');

// Promise
// Hi!
// resolved //可以发现,明明then是在 Hi 前面,却最后打印

上面代码中,Promise 新建后立即执行,所以首先输出的是Promise。然后,then方法指定的回调函数,将在当前脚本所有同步任务执行完才会执行,所以resolved最后输出。

实际上,这个运行结果相关知识点是 [ 宏任务与微任务 ] ,单独梳理在下方.这里可以先初步理解为:

JS是单线程的,至上往下运行,在声明 Promise 时实际上已经执行到了内部方法

为何 resolve() 运行后没有立即打印?

JS中用来存储待执行回调函数的队列包含2个不同特定的列队

宏队列:用来保存待执行的宏任务(回调),比如:定时器回调/ajax回调/dom事件回调

微队列:用来保存待执行的微任务(回调),比如:Promise的回调/muntation回调

JS执行时会区别这2个队列:

JS执行引擎首先必须执行所有的初始化同步任务代码

每次准备取出第一个宏任务执行前,都要将所有的微任务一个一个取出来执行

⑤ 举个异步加载图片的栗子
function loadImageAsync(url) {
return new Promise(function(resolve, reject) {
const image = new Image();

image.onload = function() {
console.log('图片加载成功')
resolve(image);
};

image.onerror = function() {
reject(new Error(`无法从 ${url} 中加载图片` ));
};
image.src = url;
});
}
loadImageAsync('正确的url') //打印图片加载成功
loadImageAsync('错误的url') //抛出异常



上面代码中,使用Promise包装了一个图片加载的异步操作。如果加载成功,就调用resolve方法,否则就调用reject方法。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V53qoz2r-1648954802668)(A_Promise系统学习笔记中的图片/image-20210926180306961.png)]

⑥ 举个用Promise对象实现的 Ajax 操作的栗子
Ajax知识点不懂的同学要去补一下: 这里可以看本人梳理的ajax笔记 --> 点我跳转

const getJSON = function(url) {
const promise = new Promise(function(resolve, reject){
const handler = function() {
if (this.readyState !== 4)  return; //当readyState 为4 时直接返回,不修改 promise 状态
if (this.status === 200) resolve(this.response); //返回状态为 200 时将状态修改为成功,且将响应内容返回
 else  reject(new Error(this.statusText)); //失败时抛出异常
};
const client = new XMLHttpRequest(); //实例化xml实例
client.open("GET", url); //下面这几行都是对xml实例进行配置,不懂的同学要去补一下ajax知识点
client.onreadystatechange = handler;
client.responseType = "json";
client.setRequestHeader("Accept", "application/json");
client.send();
});
return promise;
};

getJSON("./hong.json").then(function(json) {
console.log('Contents: ' , json);
}, function(error) {
console.error('出错了', error);
});


上面代码中,getJSON是对 XMLHttpRequest 对象的封装,用于发出一个针对 JSON 数据的 HTTP 请求,并且返回一个Promise对象。需要注意的是,在getJSON内部,resolve函数和reject函数调用时,都带有参数。

⑦ resolve() 的参数可以是另一个 Promise 实例
如果调用resolve函数和reject函数时带有参数,那么它们的参数会被传递给回调函数。reject函数的参数通常是Error对象的实例,表示抛出的错误;resolve函数的参数除了正常的值以外,还可能是另一个 Promise 实例,比如像下面这样。

const p1 = new Promise(function (resolve, reject) {});

const p2 = new Promise(function (resolve, reject) { resolve(p1) })

上面代码中,p1和p2都是 Promise 的实例,但是p2的resolve方法将p1作为参数,即一个异步操作的结果是返回另一个异步操作。

注意,这时p1的状态就会传递给p2,也就是说,p1的状态决定了p2的状态。如果p1的状态是pending,那么p2的回调函数就会等待p1的状态改变;如果p1的状态已经是resolved或者rejected,那么p2的回调函数将会立刻执行。

const p1 = new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('p1的状态改为错误')), 0)
})

const p2 = new Promise(function (resolve, reject) {
setTimeout(() => resolve(p1), 3000) //将p1 传给p2
})

p2.then(result => console.log(result),result=>console.log('失败'))
.catch(error => console.log('catch异常捕获:'+error))
//首先报错
//运行三秒后打印: 失败

上面代码运行后执行效果:
首先马上会打印一个报错 : “Uncaught (in promise) Error: p1的状态改为错误” (红色报错)
然后等3秒后再打印: ‘失败’
注意: 如果 p2.then() 中没有写 reject 回调函数(第二个参数),则会被 catch 捕获,变为catch异常捕获:Error: p1的状态改为错误
解释:
首先前面说过,promise定义时就会立即执行,所以刚开始就运行了 p1 的reject(),所以直接控制台报错了
resolve方法返回的是p1。由于p2返回的是另一个 Promise,导致p2自己的状态无效了,由p1的状态决定p2的状态
总结来说,promise返回promise这种嵌套形式,将由最内层的promise决定外层的状态
⑧ 调用resolve或reject并不会终结 Promise 的参数函数的执行
调用resolve或reject并不会终结 Promise 的参数函数的执行。

new Promise((resolve, reject) => {
resolve(1);
console.log(2);
}).then(r => {
console.log(r);
});
// 2
// 1

上面代码中,调用resolve(1)以后,后面的console.log(2)还是会执行,并且会首先打印出来。这是因为立即 resolved 的 Promise 是在本轮事件循环的末尾执行,总是晚于本轮循环的同步任务。

⑨ 建议在修改状态函数前加return
一般来说,调用resolve或reject以后,Promise 的使命就完成了,后继操作应该放到then方法里面,而不应该直接写在resolve或reject的后面。所以,最好在它们前面加上return语句,这样就不会有意外。

new Promise((resolve, reject) => {
return resolve(1);
// 后面的语句不会执行
console.log(2);
})

有同学可能就会问了,不加感觉也没啥事啊,反正我在这个函数体内就是要做这些操作,放在 resolve/reject前后好像都不影响啊! 这里我给举个实际场景

a) 不加 return 导致的错误场景举🌰
一般来说,错误发生在 Promise 内,是不会传到外部的,只会在 Promise 内部消化,详见下方API详解部分的 [②Promise.prototype.catch()](#② Promise.prototype.catch())

const promise = new Promise(function (resolve, reject) {
resolve('成功了'); //如果你加了 return , 函数执行到此步就停止了
setTimeout(function () { throw new Error('错误错误!!!!!') }, 0)
});
promise.then(function (value) { console.log(value) });
// ok
// Uncaught Error: 错误错误!!!!

上面代码中,Promise 指定在下一轮“事件循环”再抛出错误。到了那个时候,Promise 的运行已经结束了,所以这个错误是在 Promise 函数体外抛出的,会冒泡到最外层,成了未捕获的错误。

2 - API 用法详解
此处将对于所有API进行详细剖析,参照资料为 阮一峰的ES6日志

① Promise.prototype.then()
Promise 实例具有then方法,也就是说,then方法是定义在原型对象Promise.prototype上的。它的作用是为 Promise 实例添加状态改变时的回调函数。前面说过,then方法的第一个参数是resolved状态的回调函数,第二个参数(可选)是rejected状态的回调函数。

a) then方法返回的是一个新的Promise实例
then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。

getJSON("./hong.json").then(function(json) {
return json.name;
}).then(function(name) {
console.log(`My name is ${name}` )
});

上面的代码使用then方法,依次指定了两个回调函数。第一个回调函数完成以后,会将返回结果作为参数,传入第二个回调函数。

b) 采用链式的then, 会等待前一个Promise状态发生改变才会被调用
采用链式的then,可以指定一组按照次序调用的回调函数。这时,前一个回调函数,有可能返回的还是一个Promise对象(即有异步操作),这时后一个回调函数,就会等待该Promise对象的状态发生变化,才会被调用。

getJSON("./hong.json")
.then(function(json) {  return getJSON(json.name)})
.then(
    function (name) { console.log("resolved: My name is ", name)}, 
    function (err){ console.log("rejected: ", err)}
   );

上面代码中,第一个then方法指定的回调函数,返回的是另一个Promise对象。这时,第二个then方法指定的回调函数,就会等待这个新的Promise对象状态发生变化。如果变为resolved,就调用第一个回调函数,如果状态变为rejected,就调用第二个回调函数。

c) 使用箭头函数简写
如果采用箭头函数,上面的代码可以写得更简洁 (实际代码中基本都是这样写了)

getJSON("./hong.json")
.then(json => getJSON(json.name) )
.then(
      name => console.log("resolved: My name is ", name), 
    err => console.log("rejected: ", err)
   );

② Promise.prototype.catch()
Promise.prototype.catch()方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数。

a) 基本用法
getJSON('./hong.json')
.then(function(posts) {})
.catch(function(error) {
        // 处理 getJSON 和 前一个回调函数运行时发生的错误
        console.log('发生错误!', error);
     });

上面代码中,getJSON()方法返回一个 Promise 对象

如果该对象状态变为resolved,则会调用then()方法指定的回调函数;
如果异步操作抛出错误,状态就会变为rejected,就会调用catch()方法指定的回调函数,处理这个错误
另外,then()方法指定的回调函数,如果运行中抛出错误,也会被catch()方法捕获。
被 catch 方法捕获的前提是前方的 then() 方法中没有对 rejected 进行捕获处理(即没有写reject回调函数)
p.then((val) => console.log('指定成功回调:', val))
.catch((err) => console.log('在catch中进行 rejected 的处理', err));
// 等同于
p.then((val) => console.log('指定成功回调:', val))
.then(null, (err) => console.log("等同于另起一个then,只指定 rejected 的处理", err));

b) reject()方法的作用,等同于抛出错误
const promise = new Promise(function(resolve, reject) {
throw new Error('直接抛出错误');
});
promise.catch(function(error) {
console.log('异常捕获: ',error);
});
//异常捕获:  Error: 直接抛出错误

上面代码中,promise抛出一个错误,就被catch()方法指定的回调函数捕获。注意,上面的写法与下面两种写法是等价的。

/******************  写法一 ***************************************/
const promise = new Promise(function(resolve, reject) {
try {
throw new Error('直接抛出错误');
} catch(e) {
console.log('进入catch,然后再用 reject(e)抛出 ')
reject(e) 
}
});
promise.catch(function(error) {
console.log(error);
});
//进入catch,然后再用 reject(e)抛出 
//Error: 直接抛出错误

/******************  写法二 ***************************************/
const promise1 = new Promise(function(resolve, reject) {
reject(new Error('使用 reject() 抛出错误'));
});
promise1.catch(function(error) {
console.log(error);
});
//Error: 使用 reject() 抛出错误


比较上面两种写法,可以发现reject()方法的作用,等同于抛出错误,所以不必用try…catch()去承接后再去抛出了

c) 如果 Promise 状态已经被修改,再抛出错误是无效的
const promise = new Promise(function(resolve, reject) {
resolve('成功了'); //换成 reject('成功了') 结果也是一样的
throw new Error('成功后扔抛出异常');
});
promise
.then(function(value) { console.log(value) })
.catch(function(error) { console.log(error) });
// 成功了

上面代码中,Promise 在resolve/reject语句后面,再抛出错误,不会被捕获,等于没有抛出。因为 Promise 的状态一旦改变,就永久保持该状态,不会再变了(前面有说过)

d) Promise 对象的错误具有 “冒泡” 性质
Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获。

getJSON('./hong.json') //第一个promise
.then(function(post) { //第二个promise
         return getJSON(post.commentURL)
    })
.then(function(comments) { //第三个promise
    })
.catch(function(error) {
        // 处理前面三个Promise产生的错误
    });

上面代码中,一共有三个 Promise 对象(then返回的仍可能是一个Promise对象):一个由getJSON()产生,两个由then()产生。它们之中任何一个抛出的错误,都会被最后一个catch()捕获。

也是因为这个特性,有了 异常穿透问题

e) 异常穿透问题
当使用 promise 的 then 链式调用时, 可以在最后指定失败的回调
前面任何操作出了异常, 都会传到最后失败的回调中处理
getJSON('./hong.json')
 .then(function(posts) { throw new Error('抛出异常') })
    .then(res=>console.log(res),e=>console.log('被then的错误回调捕获',e) )
 .catch(function(error) {
         // 处理 getJSON 和 前一个回调函数运行时发生的错误
        console.log('错误捕获: ', error);
    });
//执行结果: 被then的错误回调捕获 Error: 抛出异常

/******************** 利用异常穿透 ****************************************/
getJSON('./hong.json')
 .then(function(posts) { throw new Error('抛出异常') })
    .then(res=>console.log(res) ) //此处差异,不指定 reject 回调,利用异常穿透传到最后
 .catch(function(error) {
        console.log('错误捕获: ', error);
    });
//执行结果:  错误捕获:  Error: 抛出异常


注:可以在每个then()的第二个回调函数中进行err处理,也可以利用异常穿透特性,到最后用catch去承接统一处理,两者一起用时,前者会生效(因为err已经将其处理,就不会再往下穿透)而走不到后面的catch[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KM2TPd8i-1648954802670)(A_Promise系统学习笔记中的图片/image-20210927105504988.png)]

f) 建议使用 catch() 进行异常处理
一般来说,不要在then()方法里面定义 Reject 状态的回调函数(即then的第二个参数),总是使用catch方法。

// bad
promise
.then(
data=> console.log('成功',data),
err=>console.log('失败了',err)
    );
/********* 好的写法 ********************/
promise
.then( data=> console.log('成功',data)) //只指定成功回调
.catch( err=>console.log('失败了',err));

上面代码中,第二种写法要好于第一种写法:

理由是第二种写法可以捕获前面then方法执行中的错误
也更接近同步的写法(try/catch)
因此, 建议总是使用catch()方法,而不使用then()方法的第二个参数。
g) 与传统 try/catch 代码块的差异
跟传统的try/catch代码块不同的是,如果没有使用catch()方法指定错误处理的回调函数,Promise 对象抛出的错误不会传递到外层代码,即不会有任何反应。

const someAsyncThing = function() {
return new Promise(function(resolve, reject) {
// 下面一行会报错,因为hong 没有声明
resolve( hong );
});
};
//Promise 的 then() 处理,但不处理异常
someAsyncThing().then(function() { console.log('只指定成功回调,不处理异常错误') });

setTimeout(() => { console.log('努力学习的汪') }, 2000);
// Uncaught (in promise) ReferenceError: hong is not defined
// 努力学习的汪

上面代码中,someAsyncThing()函数产生的 Promise 对象,内部有语法错误。

浏览器运行到这一行,会打印出错误提示Uncaught (in promise) ReferenceError: hong is not defined
但是不会退出进程、终止脚本执行, 2 秒之后还是会输出努力学习的汪。
这就是说,Promise 内部的错误不会影响到 Promise 外部的代码,通俗的说法就是“Promise 会吃掉错误”。
h) catch()方法后还能跟 then() 方法
一般总是建议,Promise 对象后面要跟catch()方法,这样可以处理 Promise 内部发生的错误。catch()方法返回的还是一个 Promise 对象,因此后面还可以接着调用then()方法。

const someAsyncThing = function() {
return new Promise(function(resolve, reject) {
// 下面一行会报错,因为 hong 没有声明
resolve( hong );
});
};

someAsyncThing()
.catch(function(error) {  console.log('捉到错误咯:', error)})
.then(function() { console.log('错误捕获后我还要浪') });
//捉到错误咯: ReferenceError: hong is not defined
//错误捕获后我还要浪

上面代码运行完catch()方法指定的回调函数,会接着运行后面那个then()方法指定的回调函数。

如果没有报错,则会跳过catch()方法。

Promise.resolve('硬是成功了')
.catch(function(error) { console.log('捉错误', error) })
.then(v => console.log('catch后面的then: ',v) );
//catch后面的then:  硬是成功了

上面的代码因为没有报错,跳过了catch()方法,直接执行后面的then()方法。此时,要是then()方法里面报错,就与前面的catch()无关了。

i) catch()方法之中,还能再抛出错误
catch()方法之中,还能再抛出错误。

const someAsyncThing = function() {
return new Promise(function(resolve, reject) {
// 下面一行会报错,因为 hong 没有声明
resolve( hong );
});
};

someAsyncThing()
.then(() =>  someOtherAsyncThing()) 
.catch(function(error) {
         console.log('ctach:', error);
         // 下面一行会报错,因为 sum 没有声明
          sum ++;
    })
.then(function() { console.log('捕获后的then()')});

// ctach: [ReferenceError: hong is not defined]
// Uncaught (in promise) ReferenceError: sum is not defined


上面代码中,catch()方法抛出一个错误,因为后面没有别的catch()方法了,导致这个错误不会被捕获,也不会传递到外层。如果改写一下,结果就不一样了。

someAsyncThing().then(function() {
return someOtherAsyncThing();
}).catch(function(error) {
console.log('catch: ', error);
// 下面一行会报错,因为 sum 没有声明
sum ++;
}).catch(function(error) {
console.log('catch()后的catch: ', error);
});
//catch:  ReferenceError: hong is not defined
//catch()后的catch:  ReferenceError: sum is not defined

上面代码中,第二个catch()方法用来捕获前一个catch()方法抛出的错误。

③ Promise.prototype.finally()
finally()方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的。

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

上面代码中,不管promise最后的状态,在执行完then或catch指定的回调函数以后,都会执行finally方法指定的回调函数。

finally方法的回调函数不接受任何参数,
这意味着没有办法知道,前面的 Promise 状态到底是fulfilled还是rejected。
这表明,finally方法里面的操作,应该是与状态无关的,不依赖于 Promise 的执行结果。
a) finally本质上是then方法的特例
promise
.finally(() => {});

// 等同于
promise
.then(
result =>  result ,
error =>  throw error
);

上面代码中,如果不使用finally方法,同样的语句需要为成功和失败两种情况各写一次。有了finally方法,则只需要写一次。

b) 它的实现
它的实现也很简单。

Promise.prototype.finally = function (callback) {
let P = this.constructor;
return this.then(
value  => P.resolve(callback()).then(() => value),
reason => P.resolve(callback()).then(() => { throw reason })
);
};

上面代码中,不管前面的 Promise 是fulfilled还是rejected,都会执行回调函数callback。

从上面的实现还可以看到,finally方法总是会返回原来的值(传入什么即传出什么)

// resolve 的值是 undefined
Promise.resolve(2).then(() => {}, () => {})

// resolve 的值是 2
Promise.resolve(2).finally(() => {})

// reject 的值是 undefined
Promise.reject(3).then(() => {}, () => {})

// reject 的值是 3
Promise.reject(3).finally(() => {})

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C7hwI6wt-1648954802671)(A_Promise系统学习笔记中的图片/image-20210927135255264.png)]

④ Promise.all()
Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。

const p = Promise.all([p1, p2, p3]);

Promise.all()方法接受一个数组作为参数,
p1、p2、p3都是 Promise 实例,如果不是,就会先调用下面讲到的Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。
另外,Promise.all()方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。
a) 返回的状态由什么决定?
p的状态由p1、p2、p3决定,分成两种情况。

只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
下面是一个具体的例子。
// 生成一个Promise对象的数组
const promises = ['hong', 1, 2, 3, 4, 5].map(item {
return getJSON( item+'.json');
});

Promise.all(promises).then(function (posts) {
// ...
}).catch(function(reason){
// ...
});

上面代码中,promises是包含 6 个 Promise 实例的数组,只有这 6 个实例的状态 都 变成fulfilled,或者其中有一个变为rejected,才会调用Promise.all方法后面的回调函数。

下面是另一个例子
const databasePromise = connectDatabase(); //假设定义了一个异步方法,此方法能拿到你需要的所有数据

const booksPromise = databasePromise     //定义一个方法,在 databasePromise() 执行后寻找其内部书本信息
.then(findAllBooks);

const userPromise = databasePromise    //定义一个方法,在 databasePromise() 执行后寻找其内部当前用户信息
.then(getCurrentUser);

Promise.all([
booksPromise,
userPromise
])
.then(([books, user]) => pickTopRecommendations(books, user));

上面代码中,booksPromise和userPromise是两个异步操作,只有等到它们的结果都返回了,才会触发pickTopRecommendations这个回调函数。

b) 如果参数中的Promise实例定义了自己的catch方法 ?
注意,如果作为参数的 Promise 实例,自己定义了catch方法,那么它一旦被rejected,并不会触发Promise.all()的catch方法。

//定义一个状态将为成功的的promise
const p1 = new Promise((resolve, reject) => { resolve('hello')})
.then(result => result)
.catch(e => e);

//定义一个将抛出错误的promise
const p2 = new Promise((resolve, reject) => { throw new Error('报错了') })
.then(result => result)
.catch(e =>{
console.log('p2自己的catch捕获: ', e)
return e; //异常获取后原样返回,不做修改
});

//调用 Promise.all 方法
Promise.all([p1, p2])
.then(result => console.log(' Promise.all 方法中的成功回调: ', result))
.catch(e => console.log(" Promise.all 方法中的catch", e));

//p2自己的catch捕获:  Error: 报错了
// Promise.all 方法中的成功回调:  (2) ['hello', Error: 报错了]


上面代码中,

p1会resolved,p2首先会rejected
但是p2有自己的catch方法,该方法返回的是一个新的 Promise 实例,p2指向的实际上是这个实例。
该实例执行完catch方法后,也会变成resolved,导致Promise.all()方法参数里面的两个实例都会resolved
因此会调用then方法指定的回调函数,而不会调用catch方法指定的回调函数
c) 如果参数中的Promise实例 没有 定义自己的catch方法 ?
如果p2没有自己的catch方法,就会调用Promise.all()的catch方法。

//定义一个状态将为成功的的promise
const p1 = new Promise((resolve, reject) => { resolve('hello')})
.then(result => result)

//定义一个将抛出错误的promise
const p2 = new Promise((resolve, reject) => { throw new Error('报错了') })
.then(result => result)

//调用 Promise.all 方法
Promise.all([p1, p2])
.then(result => console.log(' Promise.all 方法中的成功回调: ', result))
.catch(e => console.log(" Promise.all 方法中的catch", e));

// Promise.all 方法中的catch Error: 报错了

⑤ Promise.race()
Promise.race()方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。

const p = Promise.race([p1, p2, p3]);
1
上面代码中,只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。

Promise.race()方法的参数与Promise.all()方法一样,如果不是 Promise 实例,就会先调用下面讲到的Promise.resolve()方法,将参数转为 Promise 实例,再进一步处理。

a) 举个简单的🌰
如p1延时,开启了异步,内部正常是同步进行,所以p2>p3>p1,结果是P2

let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK');
}, 1000);
})
let p2 = Promise.resolve('Success');
let p3 = Promise.resolve('Oh Yeah');
//调用
const result = Promise.race([p1, p2, p3]);
console.log(result);

b) 举个应用实🌰
下面是一个例子,如果指定时间内没有获得结果,就将 Promise 的状态变为reject,否则变为resolve。

const p = Promise.race([
fetch('https://gitee.com/hongjilin'),
new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('请求超时!!!!')), 5000)
})
]);

p
.then(console.log)
.catch(console.error);

上面代码中,如果 5 秒之内fetch方法无法返回结果,变量p的状态就会变为rejected,从而触发catch方法指定的回调函数。

是不是很好用又简单

⑥ Promise.allSettled()
Promise.allSettled()方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。

只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,包装实例才会结束。

该方法由 ES2020 引入。

a) 举个简单的🌰
const promises = [
fetch('https://gitee.com/hongjilin'),
fetch('https://github.com/Hongjilin'),
fetch('./hong.json'),
];
loading = true; //请求前将 loading 改为true ; 页面出现滚动加载图标蒙层
await Promise.allSettled(promises);
loading = false;

上面代码对服务器发出三个请求,等到三个请求都结束,不管请求成功还是失败,加载的滚动图标就会消失。

b) 该方法返回的新的 Promise 实例,一旦结束,状态总是fulfilled,不会变成rejected
该方法返回的新的 Promise 实例,一旦结束,状态总是fulfilled,不会变成rejected。状态变成fulfilled后,Promise 的监听函数接收到的参数是一个数组,每个成员对应一个传入Promise.allSettled()的 Promise 实例。

const resolved = Promise.resolve('返回成功状态的promise');
const rejected = Promise.reject('返回失败状态的promise');

const allSettledPromise = Promise.allSettled([resolved, rejected]);
// Promise.allSettled 得到的新实例状态只会是 `fulfilled`
allSettledPromise.then(function (results) {
console.log(results); //注意,这是 `fulfilled` 的回调函数,只有其状态为成功才能进到这里
});
/*
[
    { "status": "fulfilled", "value": "返回成功状态的promise" },
    { "status": "rejected", "reason": "返回失败状态的promise" }
]
*/

Promise.allSettled()的返回值allSettledPromise,状态只可能变成fulfilled(注意,是 allSettledPromise 的状态,而不是内部的promise实例)
它的监听函数接收到的参数是数组results。该数组的每个成员都是一个对象,对应的是传入Promise.allSettled()的 Promise 实例。
每个对象都有status属性,该属性的值只可能是字符串fulfilled或字符串rejected。
fulfilled时,对象有value属性,rejected时有reason属性,对应两种状态的返回值。
c) 举个返回值用法的🌰
const promises = [ fetch('./hong.json'), fetch('https://gitee.com/hongjilin') ];
const results = await Promise.allSettled(promises);

// 过滤出成功的请求
const successfulPromises = results.filter(item => item.status === 'fulfilled');

// 过滤出失败的请求,并取得它们的失败原因
const errors = results
.filter(p => p.status === 'rejected')
.map(p => p.reason);

有时候,我们不关心异步操作的结果,只关心这些操作有没有结束。这时,Promise.allSettled()方法就很有用。如果没有这个方法,想要确保所有操作都结束,就很麻烦。Promise.all()方法无法做到这一点。

const urls = [ 'https://gitee.com/hongjilin' ,'https://github.com/Hongjilin'];
const requests = urls.map(x => fetch(x));
//举例用 Promise.all 尝试实现,很明显,难以实现
try {
await Promise.all(requests);
console.log('所有请求都成功。');
} catch {
console.log('至少一个请求失败,其他请求可能还没结束。');
}

上面代码中,Promise.all()无法确定所有请求都结束。想要达到这个目的,写起来很麻烦,有了Promise.allSettled(),这就很容易了

⑦ Promise.any()
ES2021 引入了Promise.any()方法。该方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例返回。只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态。

a) 与 Promise.race() 方法的区别
Promise.any()跟Promise.race()方法很像,只有一点不同,就是不会因为某个 Promise 变成rejected状态而结束。

const promises = [
fetch('https://gitee.com/hongjilin').then(() => 'a'),
fetch('https://github.com/Hongjilin').then(() => 'b'),
fetch('./hong.json').then(() => 'c'),
];
try {
const first = await Promise.any(promises);
console.log(first);
} catch (error) {
console.log(error);
}

上面代码中,Promise.any()方法的参数数组包含三个 Promise 操作。其中只要有一个变成fulfilled,Promise.any()返回的 Promise 对象就变成fulfilled。如果所有三个操作都变成rejected,那么await命令就会抛出错误。

b) Promise.any() 抛出的错误
Promise.any()抛出的错误,不是一个一般的错误,而是一个 AggregateError 实例。它相当于一个数组,每个成员对应一个被rejected的操作所抛出的错误。下面是 AggregateError 的实现示例。

new AggregateError() extends Array -> AggregateError

const err = new AggregateError();
err.push(new Error("first error"));
err.push(new Error("second error"));
throw err;

捕捉错误时,如果不用try...catch结构和 await 命令,可以像下面这样写。

Promise.any(promises).then(
(first) => {
// Any of the promises was fulfilled.
},
(error) => {
// All of the promises were rejected.
}
);

c) 再举个🌰
下面是一个例子。

const resolved = Promise.resolve('成功');
const rejected = Promise.reject('失败了');
const alsoRejected = Promise.reject('太失败了');

Promise.any([resolved, rejected, alsoRejected]).then(function (result) {
console.log(result); // 成功
});

Promise.any([rejected, alsoRejected]).catch(function (results) {
console.log(results);  //AggregateError: All promises were rejected
});

三个Promise中有一个为成功,则总的结果就是成功,三个中全部失败,才会变成失败

⑧ Promise.resolve()
有时需要将现有对象转为 Promise 对象,Promise.resolve()方法就起到这个作用。

const jsPromise = Promise.resolve($.ajax('https://gitee.com/hongjilin'));

上面代码将 jQuery 生成的deferred对象,转为一个新的 Promise 对象。

Promise.resolve()等价于下面的写法。

Promise.resolve('努力学习的汪')
// 等价于
new Promise(resolve => resolve('努力学习的汪'))

Promise.resolve()方法的参数分成四种情况

a) 参数是一个 Promise 实例
如果参数是 Promise 实例,那么Promise.resolve将不做任何修改、原封不动地返回这个实例。

b) 参数是一个thenable对象
thenable对象指的是具有then方法的对象,比如下面这个对象。

let thenable = {
    then: function(resolve, reject) {
        resolve('成功');
    }
};

Promise.resolve()方法会将这个对象转为 Promise 对象,然后就立即执行thenable对象的then()方法。

let thenable = {
    then: function(resolve, reject) { resolve('成功') }
};

let p1 = Promise.resolve(thenable);
p1.then(function (value) {
    console.log(value);  // '成功'
});

上面代码中,thenable对象的then()方法执行后,对象p1的状态就变为resolved,从而立即执行最后那个then()方法指定的回调函数,输出 ‘成功’。

c) 参数不是具有then()方法的对象,或根本就不是对象
如果参数是一个原始值,或者是一个不具有then()方法的对象,则Promise.resolve()方法返回一个新的 Promise 对象,状态为resolved。

const p = Promise.resolve('努力学习的汪');

p.then(function (s) {
console.log(s)
});
// 努力学习的汪

上面代码生成一个新的 Promise 对象的实例p。

由于字符串 努力学习的汪 不属于异步操作(判断方法是字符串对象不具有 then 方法)
返回 Promise 实例的状态从一生成就是resolved,所以回调函数会立即执行
Promise.resolve()方法的参数会同时传给回调函数作为其参数
d) 不带有任何参数
Promise.resolve()方法允许调用时不带参数,直接返回一个resolved状态的 Promise 对象。

所以,如果希望得到一个 Promise 对象,比较方便的方法就是直接调用Promise.resolve()方法。

const p = Promise.resolve();

p.then(function () {});

上面代码的变量p就是一个 Promise 对象。

需要注意的是,立即resolve()的 Promise 对象,是在本轮“事件循环”(event loop)的结束时执行,而不是在下一轮“事件循环”的开始时 --> 不懂的同学请看 JavaScript笔记中的#4事件循环模型event-loop机制 ,本人在此有进行详细的解析

setTimeout(function () {
console.log('three'); //这里是新的一轮事件循环
}, 0);

Promise.resolve().then(function () {
console.log('two'); //本轮同步代码结束后,新一轮事件循环前,就执行
});

console.log('one');

// one
// two
// three

上面代码中,setTimeout(fn, 0)在下一轮“事件循环”开始时执行,Promise.resolve()在本轮“事件循环”结束时执行,console.log('one')则是立即执行,因此最先输出。

⑨ Promise.reject()
Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected。

const p = Promise.reject('出错了');
// 等同于
const p = new Promise((resolve, reject) => reject('出错了'))

p.then(null, function (s) {
console.log(s)
});
// 出错了

上面代码生成一个 Promise 对象的实例p,状态为rejected,回调函数会立即执行。

Promise.reject()方法的参数,会原封不动地作为reject的理由,变成后续方法的参数。

Promise.reject('出错了')
.catch(e => {
console.log(e === '出错了')
})
// true

上面代码中,Promise.reject()方法的参数是一个字符串,后面catch()方法的参数e就是这个字符串。

⑩ Promise.try()
实际开发中,经常遇到一种情况:不知道或者不想区分,函数f是同步函数还是异步操作,但是想用 Promise 来处理它。因为这样就可以不管f是否包含异步操作,都用then方法指定下一步流程,用catch方法处理f抛出的错误。一般就会采用下面的写法。

Promise.resolve().then(f)

上面的写法有一个缺点,就是如果f是同步函数,那么它会在本轮事件循环的末尾执行。

const f = () => console.log('now');
Promise.resolve().then(f);
console.log('next');
// next
// now

上面代码中,函数f是同步的,但是用 Promise 包装了以后,就变成异步执行了。

那么有没有一种方法,让同步函数同步执行,异步函数异步执行,并且让它们具有统一的 API 呢?
a) 写法一 : 用async函数来写
该知识点如果不懂的可以继续往下看,这是ES6的另外一块知识点内容

const f = () => console.log('now');
(async () => f())();
console.log('next');
// now
// next

上面代码中,第二行是一个立即执行的匿名函数,会立即执行里面的async函数,因此如果f是同步的,就会得到同步的结果;如果f是异步的,就可以用then指定下一步,就像下面的写法。

(async () => f())()
.then(...)

需要注意的是,async () => f()会吃掉f()抛出的错误。所以,如果想捕获错误,要使用promise.catch方法。

(async () => f())()
.then(...)
.catch(...)

b) 写法二 : 使用new Promise()
const f = () => console.log('now');
(
 () => new Promise(
   resolve => resolve(f())
 )
)();
console.log('next');
// now
// next

上面代码也是使用立即执行的匿名函数,执行new Promise()。这种情况下,同步函数也是同步执行的。

c) Promise.try的引出
鉴于这是一个很常见的需求,所以现在有一个提案,提供Promise.try方法替代上面的写法。

const f = () => console.log('now');
Promise.try(f);
console.log('next');
// now
// next

事实上,Promise.try存在已久,Promise 库Bluebird、Q和when,早就提供了这个方法。

由于Promise.try为所有操作提供了统一的处理机制,所以如果想用then方法管理流程,最好都用Promise.try包装一下。这样有许多好处,其中一点就是可以更好地管理异常。

function getUsername(userId) {
 return database.users.get({id: userId})
 .then(function(user) {
   return user.name;
 });
}

上面代码中,database.users.get()返回一个 Promise 对象,如果抛出异步错误,可以用catch方法捕获,就像下面这样写。

database.users.get({id: userId})
.then(...)
.catch(...)

但是database.users.get()可能还会抛出同步错误(比如数据库连接错误,具体要看实现方法),这时你就不得不用try...catch去捕获。

try {
 database.users.get({id: userId})
 .then(...)
 .catch(...)
} catch (e) {
 // ...
}

上面这样的写法就很笨拙了,这时就可以统一用promise.catch()捕获所有同步和异步的错误。

Promise.try(() => database.users.get({id: userId}))
 .then(...)
 .catch(...)

事实上,Promise.try就是模拟try代码块,就像promise.catch模拟的是catch代码块。

三、自定义Promise手写
下方的Promise.prototype.then与Promise.resolve为什么一个挂载在prototype而另一个挂载在实例对象上?
解:原因是分别为静态方法与实例方法

–>上面的需要new实例化的时候自动继承实例prototype上的方法和属性,所以用实例对象.then()来调用,而下面的Promise.resolve是静态方法,不用new,是可以直接Promise.resolve()调用

此部分可以跳过不看,类似手撕源码
1-Promise的实例方法实现
1 - 初始结构搭建
html引入,该章节后续html大部分重复 除非必要,否则不再放上来

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Promise-封装 | 1 - 初始结构搭建</title>
    <script src="./promise.js"></script>
</head>
<body>
    <script>
        let p = new Promise((resolve, reject) => {
            resolve('OK');
        });
        p.then(value => {
            console.log(value);
        }, reason=>{
            console.warn(reason);
        })
    </script>
</body>
</html>

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值