Promise

一. Promise是什么?

Promise 是异步编程的一种解决方案,比传统的解决方案回调函数, 更合理和更强大。ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象 。

  • 指定回调函数方式更灵活易懂。(指指定成功的回调函数和失败的回调函数)

  • 解决异步 回调地狱 的问题。

1. 回调地狱(嵌套金字塔)

  • 当一个回调函数嵌套一个回调函数的时候

  • 就会出现一个嵌套结构

  • 当嵌套的多了就会出现回调地狱的情况

  • 比如我们发送三个 ajax 请求

    • 第一个正常发送
    • 第二个请求需要第一个请求的结果中的某一个值作为参数
    • 第三个请求需要第二个请求的结果中的某一个值作为参数
    ajax({
      url: '我是第一个请求',
      success (res) {
        // 现在发送第二个请求
        ajax({
          url: '我是第二个请求'data: { a: res.a, b: res.b },
          success (res2) {
            // 进行第三个请求
            ajax({
              url: '我是第三个请求',
              data: { a: res2.a, b: res2.b },
      				success (res3) { 
                console.log(res3) 
              }
            })
          }
        })
      }
    })
  • 回调地狱,其实就是回调函数嵌套过多导致的

  • 当代码成为这个结构以后,已经没有维护的可能了

二. Promise使用

// promise  承诺

// promise 构造函数 创建Promise对象
// 接收参数 executor(执行器函数)
var promise = new Promise((resolve, reject) => {
    // 如果成功, 调用resolve()方法传递参数, 最后执行then()方法的第一个参数
    resolve();
    // 如果失败, 调用reject()方法传递参数, 最后执行then()方法的第二个参数,或者catch()方法
    setTimeout(() => {
        reject();
    }, 1000);
});
console.log(promise);

promise.then(
    () => {
        console.log("成功");
    },
    () => {
        console.log("失败");
    }
);
// then指定成功的回调参数, catch指定失败的回调参数
promise
    .then(() => {
    console.log("成功");
})
    .catch(() => {
    console.log("失败");
});
  • 语法:
new Promise(function (resolve, reject) {
	  // resolve 表示成功的回调
	  // reject 表示失败的回调
	}).then(function (res) {
	  // 成功的函数
	}).catch(function (err) {
	  // 失败的函数
	})

.then().catch方法

.then().catch() 方法都会返回一个新的 Promise 对象。无论是处理成功的结果还是捕获错误,这两个方法的返回行为都是确保 Promise 链式调用可以继续进行。

.then().catch()方法retrun返回的值都会被离本方法最近的另一个.then()方法接收到

详细说明:
  1. .then() 方法
    • 当调用 .then() 方法时,它会接受两个参数:第一个是处理成功情况的回调函数,第二个是处理失败情况的回调函数(可选)。
    • 在调用 .then() 时,如果成功回调返回一个值,新的 Promise 就会解析该值。如果返回的是一个 Promise,新的 Promise 会”跟随”这个 Promise 的状态(即当它解决或者拒绝时,新的 Promise 也会相应地解决或拒绝)。
  2. .catch() 方法
    • .catch() 是一个特殊类型的 .then(),只接受处理失败情况的回调函数。
    • 如果前面的 Promise 链中有任何拒绝的情况(即调用 reject()),这个拒绝会被 .catch() 捕获,并执行相应的回调。
    • 同样,.catch() 也会返回一个新的 Promise,允许继续链式调用。
示例:

以下是一个简单的示例,演示如何使用 .then().catch() 的返回值:

let promise = new Promise((resolve, reject) => {
  // 模拟异步操作
  setTimeout(() => {
    // 这里用一个条件来决定 resolve 或 reject
    const success = true; 
    if (success) {
      resolve("操作成功");
    } else {
      reject("操作失败");
    }
  }, 1000);
});

// 使用 then 和 catch
promise
  .then((result) => {
    console.log(result);  // 输出: 操作成功
    return"进一步处理"; // 返回一个新值
  })
  .then((newResult) => {
    console.log(newResult); // 输出: 进一步处理
  })
  .catch((error) => {
    console.error(error); // 处理错误
  });

在这个例子中,promise 开始时是一个 Promise 对象,经过一秒后根据条件解析或拒绝。你可以看到:

  • 第一个 .then() 返回供后续处理的值”进一步处理”。
  • 如果 Promise 发生错误,错误会被 .catch() 捕获并处理。

总体来说,.then().catch() 的返回特性允许开发者方便地链式处理异步操作的结果及错误,形成灵活的控制流。

finally()方法

应用案例: 隐藏loading框

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>finally方法</title>
  </head>
  <body>
    <script>
      // 无论promise执行成功还是失败,都会执行finally方法
      var promise = new Promise((resolve, reject) => {
        resolve("成功");
      });
      //   promise
      //     .then((res) => {
      //       console.log(res);
      //       return "then方法执行"; // 返回一个新的promise
      //     })
      //     .catch((err) => {
      //       console.log(err);
      //       return "catch方法执行"; // 返回一个新的promise
      //     })
      //     .finally(() => {
      //       // 总会执行
      //       console.log("finally方法执行");
      //     });

      /* 
      假如我们在finally方法中后再调用then方法,输出是:   
      成功
      finally方法执行
      then方法执行
       */
      promise
        .then((res) => {
          console.log(res);
          return "then方法执行"; // 返回一个新的promise
        })
        .catch((err) => {
          console.log(err);
          return "catch方法执行"; // 返回一个新的promise
        })
        .finally(() => {
          // 总会执行
          console.log("finally方法执行");
        })
        .then((res) => {
          console.log(res); // 也会执行,相当于从第一个then方法返回的promise接收到之后再调用then方法
        });
    </script>
  </body>
</html>

三. Promise对象的状态

Promise 对象通过自身的状态,来控制异步操作。Promise 实例具有三种状态

异步操作未完成    (pending)
异步操作成功		(fulfilled)
异步操作失败		(rejected)

这三种的状态的变化途径只有两种。

从"未完成"到"成功"
从"未完成"到"失败"

一旦状态发生变化,就凝固了,不会再有新的状态变化。这也是 Promise 这个名字的由来,它的英语意思是”承诺”,一旦承诺成效,就不得再改变了。这也意味着,Promise 实例的状态变化只可能发生一次。

因此,Promise 的最终结果只有两种

异步操作成功,Promise 实例传回一个值(value),状态变为 fulfilled
异步操作失败,Promise 实例抛出一个错误(error),状态变为 rejected

image-20220902141409899

四. Promise对象方法

Promise 是一个对象,也是一个构造函数。

1.Promise.resolve

将现有对象转为 Promise 对象

Promise.resolve('kerwin')
// 等价于
new Promise(resolve => resolve('kerwin'))

单页面缓存的使用案例

  • 存入loaclStorage
  • 存入window的全局变量
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8"/>
    <meta name="viewport"content="width=device-width, initial-scale=1.0"/>
    <title>Promise封装ajax</title>
  </head>
  <body>
    <button>清空缓存</button>
    <script>
      function ajax(url, method ="GET", body = null) {
        var cache = ajax.cache || (ajax.cache = { data: null }); // 缓存数据挂在ajax函数上

        if (cache.data) {
          console.log("使用缓存数据");
          return Promise.resolve(cache.data);
        }

        return new Promise((resolve, reject) => {
          var xhr = new XMLHttpRequest();
          xhr.open(method, url, true);
          if (method ==="POST") {
            xhr.setRequestHeader(
            "Content-Type",
            "application/json;charset=UTF-8"
            );
          }
          xhr.send(body);
          xhr.onload = function () {
            if (/^2[0-9]{2}$/.test(xhr.status)) {
              // 请求成功
              console.log("不走缓存");
              const responseData = JSON.parse(xhr.responseText);
              ajax.cache.data = responseData; // 缓存数据
              resolve(responseData);
            } else {
              reject(
                new Error(
                  `请求失败,状态码: ${xhr.status}, 响应: ${xhr.responseText}`
                )
              );
            }
          };
          xhr.onerror = function () {
            reject(new Error("网络错误"));
          };
        });
      }

      // 第一次请求
      ajax("https://jsonplaceholder.typicode.com/todos/122")
        .then((res) => console.log("第一次请求结果:", res))
        .catch((err) => console.log("错误:", err));

      setTimeout(() => {
        // 第二次请求
        ajax("https://jsonplaceholder.typicode.com/todos/122")
          .then((res) => console.log("第二次请求结果:", res))
          .catch((err) => console.log("错误:", err));
      }, 2000);

      // 清空缓存的方法
      function clearCache() {
        ajax.cache = { data: null }; // 重置缓存
        console.log("缓存已清空");
      }

      // 可以在需要的地方调用 clearCache() 函数来清除缓存
      var clearBtn = document.querySelector("button");
      clearBtn.onclick = clearCache;
    </script>
  </body>
</html>

2.Promise.reject

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

const p = Promise.reject('error');
// 等同于
const p = new Promise((resolve, reject) => reject('error'))
var p = Promise.reject("error");
console.log(p);
p.catch((res) => console.log(res));

var promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(null);
        //   reject("error");
    }, 1000);
});

promise
    .then((res) => {
    if (res) {
        console.log(res);
    } else {
        // console.log("数据为空");
        // return Promise.reject("数据为空");
        throw new Error("数据为空"); // 抛出错误 同样也可以
    }
})
    .catch((err) => console.log(err));

3.Promise.all

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

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

p的状态由p1,p2,p3 决定,分成两种情况。

(1)只有p1p2p3的状态都变成fulfilledp的状态才会变成fulfilled,此时p1p2p3的返回值组成一个数组,传递给p的回调函数。

(2)只要p1p2p3之中有一个被rejectedp的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。

4.Promise.race

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

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

上面代码中,只要p1p2p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。

应用-->超时机制
// 只要`p1`、`p2`、`p3`之中有一个实例率先改变状态,`p`的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给`p`的回调函数。
var p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve("p1");
    }, 2000);
});
var p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve("p2");
    }, 1000);
});
var p3 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve("p3");
    }, 3000);
});
const p = Promise.race([p1, p2, p3]);
p.then((value) => {
    console.log(value);
});

function ajax(url) {
    return new Promise((resolve, reject) => {
        var xhr = new XMLHttpRequest();
        xhr.open("GET", url, true);
        xhr.send();
        xhr.onload = function () {
            if (/^2[0-9]{2,2}/.test(xhr.status)) {
                // 请求成功
                resolve(JSON.parse(xhr.responseText));
            } else {
                reject(xhr.responseText);
            }
        };
    });
}
// 通过race实现超时机制
var p1 = ajax("http://localhost:3000/news");
var p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject("超时");
    }, 2000);
});
Promise.race([p1, p2])
    .then((res) => console.log(res))
    .catch((err) => console.log("error:", err));

5.Promise.allSettled

Promise.allSettled()方法,用来确定一组异步操作是否都结束了(不管成功或失败)。所以,它的名字叫做”Settled”,包含了”fulfilled”和”rejected”两种情况。

const promises = [ ajax('/200接口'), ajax('/401接口') ];

Promise.allSettled(promises).then(results=>{
    // 过滤出成功的请求
    results.filter(item =>item.status === 'fulfilled');
    // 过滤出失败的请求
    results.filter(item=> item.status === 'rejected');
})

6.Promise.any

只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态。

Promise.any()Promise.race()方法很像,只有一点不同,就是Promise.any()不会因为某个 Promise 变成rejected状态而结束,必须等到所有参数 Promise 变成rejected状态才会结束。

// 我们想要对一组 Promise 进行并行处理,直到有一个 Promise 成功,或者全部 Promise 都失败。
// Promise.any 方法可以实现这个功能。

var p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject("p1 success");
    }, 1000);
});
var p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve("p2 success");
    }, 2000);
});
var p3 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve("p3 success");
    }, 3000);
});

Promise.any([p1, p2, p3])
    .then((res) => {
    // 任意一个成功的 Promise 都会调用这个回调函数
    // 进行登陆处理
    console.log(res);
})
    .catch((err) => {
    // 全部失败的 Promise 都会调用这个回调函数
    // 进行错误处理,例如要求注册账户
    console.log(err);
});

Promise链式调用

// 创建一个新的 Promise 对象,用于异步请求新闻数据
var resultPromise = new Promise(function (resolve, reject) {
    // 创建 XMLHttpRequest 对象
    var xhr = new XMLHttpRequest();
    // 打开一个 GET 请求到服务器,请求新闻数据
    xhr.open("GET", `http://localhost:3000/news/?author=${name}`);
    // 发送请求
    xhr.send();
    // 设置 onload 回调函数,当请求完成时被调用
    xhr.onload = function () {
        // 如果响应状态码是 2xx,表示请求成功
        if (/^2\d{2}$/.test(xhr.status)) {
            // 解析响应文本并使用 resolve 函数解决 Promise
            resolve(JSON.parse(xhr.responseText));
        } else {
            // 如果状态码不是 2xx,使用 reject 函数拒绝 Promise
            reject(new Error(xhr.statusText));
        }
    };
})
// 使用 then 方法处理新闻数据请求成功的结果
.then((data) => {
    console.log("请求新闻成功", data[0]); // 输出新闻信息
    // 返回一个新的 Promise 对象,用于请求与新闻相关的评论数据
    return new Promise(function (resolve, reject) {
        var xhr = new XMLHttpRequest();
        // 打开一个 GET 请求到服务器,请求评论数据
        xhr.open(
            "GET",
            `http://localhost:3000/comments/?newsID=${data[0].id}` // 使用新闻数据中第一条新闻的 ID
        );
        // 发送请求
        xhr.send();
        // 设置 onload 回调函数,当请求完成时被调用
        xhr.onload = function () {
            // 如果响应状态码是 2xx,表示请求成功
            if (/^2\d{2}$/.test(xhr.status)) {
                // 解析响应文本并使用 resolve 函数解决 Promise
                resolve(JSON.parse(xhr.responseText));
            } else {
                // 如果状态码不是 2xx,使用 reject 函数拒绝 Promise
                reject(new Error(xhr.statusText));
            }
        };
    });
})
// 使用 then 方法处理评论数据请求成功的结果
.then((data) => {
    console.log("请求评论成功", data);
    return "我是请求评论成功的回调函数then";
}) // 打印请求评论成功的信息
// 使用 catch 方法捕获并处理整个链中任何一个 Promise 失败的情况
.catch((error) => console.error("请求评论失败", error))
// 使用 catch 方法捕获并处理新闻数据请求失败的情况
.catch((error) => console.log("请求新闻失败", error));

console.log(resultPromise);
  • resolve()函数的参数是传递给.then()方法里的, reject()同理
  • .then()方法return的值,将会传递给链式调用中最近的下一个.then()方法
/* 封装xhr请求的函数 */
function ajax(url) {
    return new Promise((resolve, reject) => {
        var xhr = new XMLHttpRequest();
        xhr.open("GET", url, true);
        xhr.send();
        xhr.onload = function () {
            if (/^2[0-9]{2,2}/.test(xhr.status)) {
                // 请求成功
                resolve(JSON.parse(xhr.responseText));
            } else {
                reject(xhr.responseText);
            }
        };
    });
}
ajax(`http://localhost:3000/news/?author=张三`)
    .then((data) => {
    console.log(data[0].id);
    return ajax(`http://localhost:3000/comments/?newsID=${data[0].id}`);
})
    .then((comments) => {
    console.log(comments);
});

image-20241208141753977

五. 手写Promise

function KerwinPromise(executor) {
    this.status ="pending";
    this.result = undefined;
    this.cb = []
    var _this = this;

    function resolve(res) {
        if (_this.status !=="pending") return;
        // console.log(_this)
        _this.status ="fulfilled"
        _this.result = res;

        _this.cb.forEach(item => {
            item.successCB && item.successCB(_this.result)
        });
    }

    function reject(res) {
        if (_this.status !=="pending") return;
        // console.log("reject")
        _this.status ="rejected"
        _this.result = res;
        _this.cb.forEach(item => {
            item.failCB && item.failCB(_this.result)
        });
    }
    executor(resolve, reject)
}

KerwinPromise.prototype.then = function (successCB, failCB) {

    if(!successCB){
        successCB = value=>value
    }
    if(!failCB){
        failCB = error=>error
    }

    // successCB()
    return new KerwinPromise((resolve, reject) => {
        if (this.status ==="fulfilled") {
            var result = successCB && successCB(this.result)
            // console.log(result);

            if (result instanceof KerwinPromise) {
                result.then(res => {
                    // console.log(res)
                    resolve(res);
                }, err => {
                    // console.log(err)
                    reject(err)
                })
            } else {
                resolve(result);
            }
        }
        if (this.status ==="rejected") {
            var result = failCB && failCB(this.result)

            if (result instanceof KerwinPromise) {
                result.then(res => {
                    // console.log(res)
                    resolve(res);
                }, err => {
                    // console.log(err)
                    reject(err)
                })
            } else {
                reject(result);
            }
        }

        if (this.status ==="pending") {
            //收集回调
            this.cb.push({
                successCB: () => {
                    var result = successCB && successCB(this.result)

                    if (result instanceof KerwinPromise) {
                        result.then(res => {
                            // console.log(res)
                            resolve(res);
                        }, err => {
                            // console.log(err)
                            reject(err)
                        })
                    } else {
                        resolve(result);
                    }
                },
                failCB: () => {
                    var result = failCB && failCB(this.result)
                    if (result instanceof KerwinPromise) {
                        result.then(res => {
                            // console.log(res)
                            resolve(res);
                        }, err => {
                            // console.log(err)
                            reject(err)
                        })
                    } else {
                        reject(result);
                    }
                }
            })
        }
    })
}

KerwinPromise.prototype.catch= function(failCB){
    this.then(undefined,failCB)
}

六. Async与Await

1.Async

async 函数,使得异步操作变得更加方便。

  • 更好的语义。
  • 返回值是 Promise。
async function test(){
	
}
test()

2.Await

await命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值。

async function test(){
    var res1 =  await ajax("http://localhost:3000/news1")
    var res2 =  await ajax("http://localhost:3000/news2")
    return res2
}

test().then(res=>{
	console.log("返回结果",res)
}).catch(err=>{
	console.log("err",err)
})

3.错误处理

try{
    var res1 =  await ajax("http://localhost:3000/news1")
    var res2 =  await ajax("http://localhost:3000/news2")
}catch(err){
	console.log("err",err)
}