总结Promise(一)

92 阅读17分钟

1.背景

Promise之前的异步操作都是基于回调函数实现的

栗如:

readFile("./data.txt", (error, result) => {
  // 这个回调函数将在任务完成后被调用,返回最终的 `error` 或 `result`。
  // 任何依赖于返回结果的操作都必须在这个回调函数内定义。
});
// `readFile` 请求被发出后,此处的代码会立即执行。
// 它不会等待回调函数被调用,因此使 `readFile` 成为了“异步”的。

回调函数(回头调用)

英文定义:A callback is a function that is passed as an argument to another function and is executed after its parent function has completed。A callback is a function that is passed as an argument to another function and is executed after its parent function has completed。

实际上回调函数是一个传递给另一个函数的参数,等主函数执行完毕后,再执行回调函数

2.Promise是什么

  1. Promise是一个代理,他代表一个在创建Promise时 不一定已知的值。
  2. 它允许你将处理程序与异步操作的最终成功值或失败原因联系起来。
  3. Promise() 构造函数允许将基于回调的 API 转换为基于 Promise 的 API。

3.如何使用Promise

语法

new Promise(executor)

参数(executor)

  1. 就是一个函数。他接收两个函数作为参数:resolve 和 reject 。
  2. executor 是将回调函数的结果与 Promise 关联在一起的自定义代码
  3. executor 中抛出的任何错误都会导致Promise被拒绝,并且返回值都将被忽略。
  4. executor 函数用于封装某些接受回调函数作为参数的异步操作
这里细抠一下(返回值都将被忽略)这句话
const promise = new Promise((resolve, reject) => {
  // executor 函数
  try {
    const result = someAsyncOperation();
    resolve(result); // 假设 someAsyncOperation 返回一个值
  } catch (error) {
    reject(error); // 如果异步操作失败,调用 reject
  }
​
  // executor 返回的任何值都不会影响 Promise 的状态
  // 因此这里的 return 语句实际上没有意义
  return 'This value will be ignored';
});

在这个例子中,executor 函数尝试执行一个异步操作,并且可能返回一个值。但是,无论 return 语句返回什么,这个值都不会影响 Promise 的状态,

executor 函数中的 return 语句仅影响控制流程,调整函数某个部分是否执行。因为 Promise 的状态是由 resolvereject 函数调用决定的。

返回值

使用new关键字调用Promise 构造函数时,他会返回一个Promise对象,当resolve 或者 reject 被调用时。该Promise对象就会变为已解决。

当然,如果调用resolve或者reject的时候,传入另一个Promise对象作为参数,可以说该Promise对象已解决但仍未敲定

已解决但仍未敲定(举个栗崽)
const promiseA = new Promise((resolve, reject) => {
  setTimeout(() => resolve('Promise A resolved'), 1000);
});
​
const promiseB = new Promise((resolve, reject) => {
  setTimeout(() => resolve(promiseA), 500); // 传递另一个 Promise
});
​
promiseB.then(result => {
  console.log('Promise B resolved with:', result);
});

在这里PromiseB调用了resolve , 并将PromiseA传入到回调函数作为参数

在0.5s后,Promise的状态是已解决 ,由于 PromiseA是在1s后解决,所以PromiseB仍然处于被挂起(待定)的状态,直到PromieA被敲定。

总结来说,当你在 resolvereject 中传递另一个 Promise 时,原始的 Promise 会“跟随”这个新 Promise 的状态。一旦新 Promise 被敲定,原始 Promise 也将随之敲定,反映出新 Promise 的最终状态。

PS:

  1. 如果 resolveFunc 被调用时没有参数,即 resolveFunc();,那么当前的 Promise 会被兑现,但是其兑现的值为 undefined
  2. 如果 rejectFunc 被调用时没有参数,即 rejectFunc();,那么当前的 Promise 会被拒绝,但是其拒绝的原因也是 undefined

例如,上面的基于回调的 readFile API 可以转换为基于 Promise 的 API。

const readFilePromise = (path) =>
  new Promise((resolve, reject) => {
    readFile(path, (error, result) => {
      if (error) {
        reject(error);
      } else {
        resolve(result);
      }
    });
  });
​
readFilePromise("./data.txt")
  .then((result) => console.log(result))
  .catch((error) => console.error("读取文件失败"));

resolvereject 回调仅在 executor 函数的作用域内可用,这意味着在构造 promise 之后无法访问它们。如果你想在决定如何解决之前先构造 promise,可以使用 Promise.withResolvers() 方法,该方法暴露了 resolve and reject 函数。

Promise.withResolvers() 【Since March 2024, this feature works across the latest devices and browser versions. This feature might not work in older devices or browsers.】

所以更多时候可能需要封装

我测试用的是这里的API :(全部免费)github.com/public-apis…

首先写一个普通的GET请求

var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
    if (xhr.readyState === 4) { 
        if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
            alert(xhr.responseText);
        } else {
            alert('Failed');
        }
    }
};
xhr.open('get', 'https://dog.ceo/api/breeds/image/random', true); 
xhr.send();

让一个函数具有promise的功能

function myAsyncFunction(url) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open("GET", url);
    xhr.onload = () => resolve(xhr.responseText);
    xhr.onerror = () => reject(xhr.statusText);
    xhr.send();
  });
}

封装

以fetch为例

const createWithResolers = () => {
    let resolve, reject
    const promise = new Promise((res, rej) => {
        resolve = res
        reject = rej
    })
    return { promise, resolve, reject }
}
​
const fetchResource = (url) => {
    const { promise, resolve, reject } = createWithResolers()
    fetch(url)
        .then((response) => {
            if (!response.ok) {
                throw new Error('Failed' + response.statusText);
            } // .then 方法是 fetch 返回的 Promise 的一部分,它会在请求成功完成时调用。参数 response 是一个 Response 对象,包含了服务器的响应信息。
            return response.json() //这行代码在请求成功时被调用,它将 Response 对象转换成 JSON 格式的数据。response.json() 本身也返回一个 Promise,该 Promise 在 JSON 数据解析完成后解析为实际的 JSON 对象。
        })
        .then((data) => {
            resolve(data)
        })//这个 .then 方法是在 response.json() 返回的 Promise 上调用的,它会在 JSON 数据解析成功后被调用。参数 data 是解析后的 JSON 数据
        .catch((error) => {
            reject(error)
        })
    return promise
}
​
fetchResource('https://dog.ceo/api/breeds/image/random')
    .then((data) => { console.log(data) })
    .catch((error) => { console.log(error) })

卡皮巴拉~~~-

我们发现这是Promise的链式调用(之后会总结到)。

哒哒哒哒~~~~

以下是摘自MDN的 Promise 流程概述和某些理解:

  1. 在构造函数生成新的 Promise 对象时,它还会生成一对相应的 resolveFuncrejectFunc 函数;它们与 Promise 对象“绑定”在一起。

  2. executor 通常会封装某些提供基于回调的 API 的异步操作。回调函数(传给原始回调 API 的函数)在 executor 代码中定义,因此它可以访问 resolveFuncrejectFunc

  3. executor 是同步调用的(在构造 Promise 时立即调用) ,并将 resolveFuncrejectFunc 函数作为传入参数。

  4. executor中的代码有机会执行某些操作。异步任务的最终完成通过resolveFunc或rejectFunc引起的副作用与 Promise 实例进行通信。这个副作用让Promise对象变为“已解决”状态。

    • 如果先调用 resolveFunc,则传入的值将解决

    • Promise 可能会保持待定状态(如果传入了另一个 thenable 对象),变为已兑现状态(在传入非 thenable 值的大多数情况下),或者变为已拒绝状态(在解析值无效的情况下)。

    • 如果先调用 rejectFunc,则 Promise 立即变为已拒绝状态。

    • 一旦 resolveFuncrejectFunc 中的一个被调用,Promise 将保持解决状态。只有第一次调用 resolveFuncrejectFunc 会影响 Promise 的最终状态,随后对任一函数的调用都不能更改兑现值或拒绝原因,也不能将其最终状态从“已兑现”转换为“已拒绝”或相反。

      const createPromise = () => {
          return new Promise((resolve, reject) => {
              // 立即调用 resolve 函数
              resolve('Resolved value');
      ​
              // 尝试再次调用 resolve 函数
              setTimeout(() => {
                  console.log('Trying to resolve again...');
                  resolve('This will be ignored');
              }, 1000);
      ​
              // 同样尝试再次调用 reject 函数
              setTimeout(() => {
                  console.log('Trying to reject...');
                  reject(new Error('This will also be ignored'));
              }, 2000);
          });
      };
      ​
      const myPromise = createPromise();
      ​
      myPromise
          .then(value => {
              console.log('Promise resolved with:', value);
          })
          .catch(reason => {
              console.log('Promise rejected with:', reason);
          })
      

      打印结果

    • 如果 executor 抛出错误,则 Promise 被拒绝。但是,如果 resolveFunc 或 rejectFunc 中的一个已经被调用(因此 Promise 已经被解决),则忽略该错误,这是因为 Promise 的状态一旦被设定为“已解决”或“已拒绝”,就成为永久状态,不能再次改变

    • 解决 Promise 不一定会导致 Promise 变为已兑现或已拒绝(即已敲定)。Promise 可能仍处于待定状态,因为它可能是用另一个 thenable 对象解决的,但它的最终状态将与已解决的 thenable 对象一致。

  5. 一旦 Promise 敲定,它会(异步地)调用任何通过 then()catch()finally() 关联的进一步处理程序。最终的兑现值或拒绝原因在调用时作为输入参数传给兑现和拒绝处理程序。

resolve

  • 如果它被调用时传入了一个 thenable 对象(包括另一个 Promise 实例),则该 thenable 对象的 then 方法将被保存并在未来被调用(它总是异步调用)。then 方法将被调用并传入两个回调函数,这两个函数的行为与传递给 executor 函数的 resolveFuncrejectFunc 函数完全相同。如果调用 then 方法时出现错误,则当前的 Promise 对象会被拒绝并抛出这个错误。

    这意味着像下面这样的代码

    new Promise((resolve, reject) => {
      resolve(thenable);
    });
    

    大致相当于:

    new Promise((resolve, reject) => {
      try {
        thenable.then(
          (value) => resolve(value),
          (reason) => reject(reason),
        );
      } catch (e) {
        reject(e);
      }
    });
    

    但是在 二者,有如下区别:

    • 同步调用resolve 函数被立即调用,将 thenable 作为参数传递。这意味着 Promise 在构造时立刻进入解决状态。
    • 再次调用无效:由于 Promise 的状态一旦解决或拒绝就永久固定,因此之后再尝试调用 resolvereject 将不会有作用。
    • 不等待 thenable 的结果:这种方式不关心 thenable 本身的解决或拒绝状态。Promise 的状态在构造时就被固定,而不考虑 thenable 的最终结果
    • 异步调用then 方法是异步调用的,这意味着它不会立即执行。
    • 等待 thenable 的结果:这种方式会等待 thenable 的解决或拒绝状态。当 thenable 解决时,它会调用 resolve 函数,并将解决值作为参数传递。如果 thenable 被拒绝,它会调用 reject 函数,并将拒绝原因作为参数传递。
    • 处理错误try...catch 块用于捕获在调用 then 方法时可能发生的任何错误,并通过 reject 函数将错误作为拒绝原因传递给新的 Promise。### 1.背景

Promise之前的异步操作都是基于回调函数实现的

栗如:

readFile("./data.txt", (error, result) => {
  // 这个回调函数将在任务完成后被调用,返回最终的 `error` 或 `result`。
  // 任何依赖于返回结果的操作都必须在这个回调函数内定义。
});
// `readFile` 请求被发出后,此处的代码会立即执行。
// 它不会等待回调函数被调用,因此使 `readFile` 成为了“异步”的。

回调函数(回头调用)

英文定义:A callback is a function that is passed as an argument to another function and is executed after its parent function has completed。A callback is a function that is passed as an argument to another function and is executed after its parent function has completed。

实际上回调函数是一个传递给另一个函数的参数,等主函数执行完毕后,再执行回调函数

2.Promise是什么

  1. Promise是一个代理,他代表一个在创建Promise时 不一定已知的值。
  2. 它允许你将处理程序与异步操作的最终成功值或失败原因联系起来。
  3. Promise() 构造函数允许将基于回调的 API 转换为基于 Promise 的 API。

3.如何使用Promise

语法

new Promise(executor)

参数(executor)

  1. 就是一个函数。他接收两个函数作为参数:resolve 和 reject 。
  2. executor 是将回调函数的结果与 Promise 关联在一起的自定义代码
  3. executor 中抛出的任何错误都会导致Promise被拒绝,并且返回值都将被忽略。
  4. executor 函数用于封装某些接受回调函数作为参数的异步操作
这里细抠一下(返回值都将被忽略)这句话
const promise = new Promise((resolve, reject) => {
  // executor 函数
  try {
    const result = someAsyncOperation();
    resolve(result); // 假设 someAsyncOperation 返回一个值
  } catch (error) {
    reject(error); // 如果异步操作失败,调用 reject
  }
​
  // executor 返回的任何值都不会影响 Promise 的状态
  // 因此这里的 return 语句实际上没有意义
  return 'This value will be ignored';
});

在这个例子中,executor 函数尝试执行一个异步操作,并且可能返回一个值。但是,无论 return 语句返回什么,这个值都不会影响 Promise 的状态,

executor 函数中的 return 语句仅影响控制流程,调整函数某个部分是否执行。因为 Promise 的状态是由 resolvereject 函数调用决定的。

返回值

使用new关键字调用Promise 构造函数时,他会返回一个Promise对象,当resolve 或者 reject 被调用时。该Promise对象就会变为已解决。

当然,如果调用resolve或者reject的时候,传入另一个Promise对象作为参数,可以说该Promise对象已解决但仍未敲定

已解决但仍未敲定(举个栗崽)
const promiseA = new Promise((resolve, reject) => {
  setTimeout(() => resolve('Promise A resolved'), 1000);
});
​
const promiseB = new Promise((resolve, reject) => {
  setTimeout(() => resolve(promiseA), 500); // 传递另一个 Promise
});
​
promiseB.then(result => {
  console.log('Promise B resolved with:', result);
});

在这里PromiseB调用了resolve , 并将PromiseA传入到回调函数作为参数

在0.5s后,Promise的状态是已解决 ,由于 PromiseA是在1s后解决,所以PromiseB仍然处于被挂起(待定)的状态,直到PromieA被敲定。

总结来说,当你在 resolvereject 中传递另一个 Promise 时,原始的 Promise 会“跟随”这个新 Promise 的状态。一旦新 Promise 被敲定,原始 Promise 也将随之敲定,反映出新 Promise 的最终状态。

PS:

  1. 如果 resolveFunc 被调用时没有参数,即 resolveFunc();,那么当前的 Promise 会被兑现,但是其兑现的值为 undefined
  2. 如果 rejectFunc 被调用时没有参数,即 rejectFunc();,那么当前的 Promise 会被拒绝,但是其拒绝的原因也是 undefined

例如,上面的基于回调的 readFile API 可以转换为基于 Promise 的 API。

const readFilePromise = (path) =>
  new Promise((resolve, reject) => {
    readFile(path, (error, result) => {
      if (error) {
        reject(error);
      } else {
        resolve(result);
      }
    });
  });
​
readFilePromise("./data.txt")
  .then((result) => console.log(result))
  .catch((error) => console.error("读取文件失败"));

resolvereject 回调仅在 executor 函数的作用域内可用,这意味着在构造 promise 之后无法访问它们。如果你想在决定如何解决之前先构造 promise,可以使用 Promise.withResolvers() 方法,该方法暴露了 resolve and reject 函数。

Promise.withResolvers() 【Since March 2024, this feature works across the latest devices and browser versions. This feature might not work in older devices or browsers.】

所以更多时候可能需要封装

我测试用的是这里的API :(全部免费)github.com/public-apis…

首先写一个普通的GET请求

var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
    if (xhr.readyState === 4) { 
        if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
            alert(xhr.responseText);
        } else {
            alert('Failed');
        }
    }
};
xhr.open('get', 'https://dog.ceo/api/breeds/image/random', true); 
xhr.send();

让一个函数具有promise的功能

function myAsyncFunction(url) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open("GET", url);
    xhr.onload = () => resolve(xhr.responseText);
    xhr.onerror = () => reject(xhr.statusText);
    xhr.send();
  });
}

封装

以fetch为例

const createWithResolers = () => {
    let resolve, reject
    const promise = new Promise((res, rej) => {
        resolve = res
        reject = rej
    })
    return { promise, resolve, reject }
}
​
const fetchResource = (url) => {
    const { promise, resolve, reject } = createWithResolers()
    fetch(url)
        .then((response) => {
            if (!response.ok) {
                throw new Error('Failed' + response.statusText);
            } // .then 方法是 fetch 返回的 Promise 的一部分,它会在请求成功完成时调用。参数 response 是一个 Response 对象,包含了服务器的响应信息。
            return response.json() //这行代码在请求成功时被调用,它将 Response 对象转换成 JSON 格式的数据。response.json() 本身也返回一个 Promise,该 Promise 在 JSON 数据解析完成后解析为实际的 JSON 对象。
        })
        .then((data) => {
            resolve(data)
        })//这个 .then 方法是在 response.json() 返回的 Promise 上调用的,它会在 JSON 数据解析成功后被调用。参数 data 是解析后的 JSON 数据
        .catch((error) => {
            reject(error)
        })
    return promise
}
​
fetchResource('https://dog.ceo/api/breeds/image/random')
    .then((data) => { console.log(data) })
    .catch((error) => { console.log(error) })

卡皮巴拉~~~-

我们发现这是Promise的链式调用(之后会总结到)。

哒哒哒哒~~~~

以下是摘自MDN的 Promise 流程概述和某些理解:

  1. 在构造函数生成新的 Promise 对象时,它还会生成一对相应的 resolveFuncrejectFunc 函数;它们与 Promise 对象“绑定”在一起。

  2. executor 通常会封装某些提供基于回调的 API 的异步操作。回调函数(传给原始回调 API 的函数)在 executor 代码中定义,因此它可以访问 resolveFuncrejectFunc

  3. executor 是同步调用的(在构造 Promise 时立即调用) ,并将 resolveFuncrejectFunc 函数作为传入参数。

  4. executor中的代码有机会执行某些操作。异步任务的最终完成通过resolveFunc或rejectFunc引起的副作用与 Promise 实例进行通信。这个副作用让Promise对象变为“已解决”状态。

    • 如果先调用 resolveFunc,则传入的值将解决

    • Promise 可能会保持待定状态(如果传入了另一个 thenable 对象),变为已兑现状态(在传入非 thenable 值的大多数情况下),或者变为已拒绝状态(在解析值无效的情况下)。

    • 如果先调用 rejectFunc,则 Promise 立即变为已拒绝状态。

    • 一旦 resolveFuncrejectFunc 中的一个被调用,Promise 将保持解决状态。只有第一次调用 resolveFuncrejectFunc 会影响 Promise 的最终状态,随后对任一函数的调用都不能更改兑现值或拒绝原因,也不能将其最终状态从“已兑现”转换为“已拒绝”或相反。

      const createPromise = () => {
          return new Promise((resolve, reject) => {
              // 立即调用 resolve 函数
              resolve('Resolved value');
      ​
              // 尝试再次调用 resolve 函数
              setTimeout(() => {
                  console.log('Trying to resolve again...');
                  resolve('This will be ignored');
              }, 1000);
      ​
              // 同样尝试再次调用 reject 函数
              setTimeout(() => {
                  console.log('Trying to reject...');
                  reject(new Error('This will also be ignored'));
              }, 2000);
          });
      };
      ​
      const myPromise = createPromise();
      ​
      myPromise
          .then(value => {
              console.log('Promise resolved with:', value);
          })
          .catch(reason => {
              console.log('Promise rejected with:', reason);
          })
      

      打印结果

    • 如果 executor 抛出错误,则 Promise 被拒绝。但是,如果 resolveFunc 或 rejectFunc 中的一个已经被调用(因此 Promise 已经被解决),则忽略该错误,这是因为 Promise 的状态一旦被设定为“已解决”或“已拒绝”,就成为永久状态,不能再次改变

    • 解决 Promise 不一定会导致 Promise 变为已兑现或已拒绝(即已敲定)。Promise 可能仍处于待定状态,因为它可能是用另一个 thenable 对象解决的,但它的最终状态将与已解决的 thenable 对象一致。

  5. 一旦 Promise 敲定,它会(异步地)调用任何通过 then()catch()finally() 关联的进一步处理程序。最终的兑现值或拒绝原因在调用时作为输入参数传给兑现和拒绝处理程序。

resolve

  • 如果它被调用时传入了一个 thenable 对象(包括另一个 Promise 实例),则该 thenable 对象的 then 方法将被保存并在未来被调用(它总是异步调用)。then 方法将被调用并传入两个回调函数,这两个函数的行为与传递给 executor 函数的 resolveFuncrejectFunc 函数完全相同。如果调用 then 方法时出现错误,则当前的 Promise 对象会被拒绝并抛出这个错误。

    这意味着像下面这样的代码

    new Promise((resolve, reject) => {
      resolve(thenable);
    });
    

    大致相当于:

    new Promise((resolve, reject) => {
      try {
        thenable.then(
          (value) => resolve(value),
          (reason) => reject(reason),
        );
      } catch (e) {
        reject(e);
      }
    });
    

    但是在 二者,有如下区别:

    • 同步调用resolve 函数被立即调用,将 thenable 作为参数传递。这意味着 Promise 在构造时立刻进入解决状态。
    • 再次调用无效:由于 Promise 的状态一旦解决或拒绝就永久固定,因此之后再尝试调用 resolvereject 将不会有作用。
    • 不等待 thenable 的结果:这种方式不关心 thenable 本身的解决或拒绝状态。Promise 的状态在构造时就被固定,而不考虑 thenable 的最终结果
    • 异步调用then 方法是异步调用的,这意味着它不会立即执行。
    • 等待 thenable 的结果:这种方式会等待 thenable 的解决或拒绝状态。当 thenable 解决时,它会调用 resolve 函数,并将解决值作为参数传递。如果 thenable 被拒绝,它会调用 reject 函数,并将拒绝原因作为参数传递。
    • 处理错误try...catch 块用于捕获在调用 then 方法时可能发生的任何错误,并通过 reject 函数将错误作为拒绝原因传递给新的 Promise。