# 👉 关于 Promise 的一些事

# Promise 为何出现

# 异步回调影响编码方式和容易出现地狱回调。

因为 JavaScript 是一门单线程的脚本语言,为了解决单线程容易出现的排队阻塞情况和提高运行效率,提出了异步回调解决方案。即针对异步任务,会将对应的异步回调放至任务队列,等待同步任务处理完毕后再通过事件循环机制读取任务队列执行其中的异步回调事件。

而伴随着异步回调出现与应用,编程方式表现出一定的问题,如:

  1. 异步回调易导致代码的逻辑不连贯、不线性,非常不符合人的直觉,即异步回调影响到我们的编码方式。

  2. 异步回调一旦嵌套了太多的回调函数,就很容易使得陷入了回调地狱,并且针对每个任务都有两种可能的结果(成功/失败)都需要进行处理,大大增加代码混乱程度。

// version 1
// 执行状态回调函数
function onResolve(response) {
    console.log(response);
}
function onReject(error) {
    console.log(error);
}

let xhr = new XMLHttpRequest();

// 设置了三个请求状态回调
xhr.ontimeout = function(e) {
    onReject(e);
};
xhr.onerror = function(e) {
    onReject(e);
};
xhr.onreadystatechange = function() {
    onResolve(xhr.response);
};

// 设置请求类型,URL,是否同步信息
let URL = "http://baidu.com";
xhr.open("Get", URL, true);

// 设置参数
xhr.timeout = 3000; // 设置 xhr 请求的超时时间
xhr.responseType = "application/json"; // 设置响应返回的数据格式

// 发出请求
xhr.send();
// version 2,为了解决 version 1 回调不连贯问题,将其封装
const request = {
    method: "get",
    url: "http://baidu.com",
    responseType: "application/json",
    timeout: 5000,
    async: true,
};

const xhrFetch = (request, resolve, reject) => {
    let xhr = new XMLHttpRequest();
    xhr.ontimeout = function(e) {
        reject(e);
    };
    xhr.onerror = function(e) {
        reject(e);
    };
    xhr.onreadystatechange = function() {
        resolve(xhr.response);
    };

    xhr.open(request.method, request.url, request.async);
    xhr.timeout = request.timeout || 3000;
    xhr.responseType = request.responseType || "application/json";
    xhr.send();
};

xhrFetch(request, onResolve, onReject);
// version3 如果有多个异步回调,并且下一个回调依赖上一个回调的结果,容易出现地狱回调问题
xhrFetch(
    request,

    function(request, res) {
        const result1 = res;
        // 发起第二个请求
        request.url = result1.nextUrl;
        xhrFetch(
            request,
            function(res) {
                const result2 = res;

                // 发起第三个请求
                request.url = result2.nextUrl;
                xhrFetch(
                    request,
                    function(res) {
                        const result3 = res;

                        // 发起第三个请求
                        request.url = result3.nextUrl;

                        //....我是谁,我在哪
                    },
                    onReject
                );
            },
            onReject
        );
    },
    onReject
);

因此 Promise 的出现就是为了消灭嵌套调用和多次错误处理,利用 Promise 改造一下上述的函数:

const xhrFetch = request => {
    return new Promise((resolve, reject) => {

      let xhr = new XMLHttpRequest();
      xhr.open(request.method, request.url, request.async);

      xhr.ontimeout = function(e) {
        reject(e);
      };
      xhr.onerror = function(e) {
        reject(e);
      };
      xhr.onreadystatechange = function() {
          if (this.readyState === 4) {
              if (this.status === 200) {
                  resolve(this.response, this)
              } else {
                  let error = {
                      code: this.status,
                      response: this.response
                  }
                  reject(error, this)
              }
          }
      };
      xhr.send();
    });
};


const request = (url) => {
    url,
    method: "get",
    responseType: "application/json",
    timeout: 5000,
    async: true
};

const xhr1 = xhrFetch(request("http://baidu.com"));

const xh2 = xhr1.then((response1) => {
    console.log(response1)
    return XFetch(request(response1.url))
});

const xhr3 = xhr2.then((response2) => {
    console.log(response2)
    return XFetch(request(response2.url))
}).catch((error) => {
    console.log(error)
})

这里有 xhr1、xhr2、xhr3 三个 Promise 对象,无论哪个对象里面抛出异常,都可以通过最后一个对象 xhe.catch 来捕获异常,通过这种方式可以将所有 Promise 对象的错误合并到一个函数来处理,这样就解决了每个任务都需要单独处理异常的问题。

之所以可以使用最后一个对象来捕获所有异常,是因为 Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被 catch 语句捕获为止。具备了这样“冒泡”的特性后,就不需要在每个 Promise 对象中单独捕获异常了。

# Promise 基础知识

  • Promise 有三种状态: pending(进行中)、fulfilled(已成功)、 rejected(已失败),状态一旦改变,就不可逆或不可再更改。为了方便,也用 resolve 统一指 fulfilled 状态。

  • Promise 构造函数接受一个函数作为参数,该函数的两个参数分别是 resolve 和 reject。

    resolve 会在异步操作成功时调用(pending -> fulfilled),讲异步操作的结果传递给 then。

    reject 则会在异步操作失败时调用(pending -> rejectd),将异步操作过程报出的错误作为参数传递出去。

  • Promise 之所以能够链式调用,因为 Promise 的 then/catch 方法执行后会返回一个新的 Promise 实例。

  • Promise 的 executor(执行器)中的代码会被同步调用。

  • Promise 的回调是基于 MicroTask(微任务),而 setTimeout 属于一个 MacroTask (任务/宏任务),每次宏任务执行完后会通过事件循环机制清空当前的微任务队列。