Promise

目录

是什么

为什么需要Promise

Promise的使用

Promise对象

状态

方法

then()

then只填写一个参数

then的链式调用

catch()

错误集中处理

错误传递

finally()

all()

race()

resolve()和reject()

Promise对象的具体使用

async/await 语法

相关疑问

回调地狱

Promise对象解决回调地狱的链式调用方案

只有异步请求才能使用Promise对象吗

为什么Fetch API和axios请求会返回Promise对象

所有的异步请求都会返回Promise对象吗


是什么

Promise 对象是 JavaScript 内置的,用于处理异步操作的对象。是一种异步编程的解决方案,在异步编程中,我们经常需要等待某个操作(如网络请求、定时器回调等)完成后才能继续执行后续的代码。Promise 提供了一种优雅的方式来处理这种情况,避免了传统的回调函数嵌套(也称为“回调地狱”)的问题。

PS:Promise是JavaScript本身自带的,而不是浏览器单独提供的。它是JavaScript ES6(ECMAScript 2015)规范中新增的一个内置对象,用于处理异步操作。Promise对象代表了一个异步操作的最终完成(或失败)及其结果值的状态。

虽然Promise是JavaScript的一部分,但并非所有的JavaScript运行环境都直接支持Promise。在一些旧版本的浏览器或JavaScript环境中,可能需要使用额外的库(如es6-promise)来提供Promise的功能。但在现代浏览器中,Promise已经被广泛支持,并且成为了处理异步操作的重要工具之一。


为什么需要Promise

Promise是JavaScript中用于处理异步操作的一个特性,它并不是JavaScript一开始就有的,而是在后续的版本中引入的。具体来说,Promise是在ECMAScript 2015(也称为ES6)中正式推出的,作为处理异步操作的一种方式。

在Promise之前,JavaScript中的异步操作通常通过回调函数来处理,但这种方式在复杂的异步流程中容易导致“回调地狱”问题,使得代码难以阅读和维护。Promise的出现提供了一种更加优雅和可维护的方式来处理异步操作。


Promise的使用

Promise对象

Promise 本身并不是一个用于发起请求的机制,而是一种处理异步操作结果的方式(异步处理机制)。它表示一个最终可能完成(也可能被拒绝)的异步操作的最终完成(或失败)及其结果值的方法

状态

Promise对象有三种状态:

  • Pending(待定):初始状态,既不是成功,也不是失败状态。
  • Fulfilled(已实现):意味着操作成功完成。
  • Rejected(已拒绝):意味着操作失败。

一旦 Promise 的状态从 pending 变为 fulfilled 或 rejected,这个状态就是不可变的,也就是说 Promise 的结果只能被解析一次(只能表示一个异步操作的成功或失败)。

PS:以下是三种状态的详细说明

待定状态:这是 Promise 对象刚被创建时的状态,表示异步操作还未完成。

已实现:一旦异步操作成功完成,Promise 就会被标记为 fulfilled 状态,并且其关联的结果值(即异步操作的成功结果)就会被传递给在 .then() 方法中注册的回调函数。

已拒绝:如果异步操作因某种原因未能成功完成(例如网络请求失败、抛出了异常等),Promise 就会被标记为 rejected 状态,并且其关联的失败原因(通常是一个错误对象)就会被传递给在 .catch() 方法中注册的回调函数。


方法

then()
  • then() 方法返回一个新的 Promise 实例,并接收两个可选的函数作为参数:onFulfilled 和 onRejected
  • onFulfilled 会在 Promise 被解决(fulfilled)时调用,并传入解决的值。
  • onRejected 会在 Promise 被拒绝(rejected)时调用,并传入拒绝的原因。

Promise 对象在某一时刻只能处于“已解决”(fulfilled)或“已拒绝”(rejected)的单一状态,而不会同时处于两种状态。

虽然Promise状态发生变化只能表示一个异步操作的成功或失败。这并不意味着你不能在Promise的链式调用中处理成功和失败的情况。你可以使用.then()方法来处理成功的情况,并使用.catch()(或链式调用中的第二个.then()参数)来处理失败的情况。

示例:

Promise 本身并不直接发起任何请求。它是一个表示异步操作最终完成(或失败)及其结果值的对象。以下示例通过创建Promise来模拟一个异步请求。

// 假设我们有一个返回 Promise 的函数  
function fetchData() {  
    return new Promise((resolve, reject) => {  
        // 假设我们进行某种异步操作,如网络请求  
        setTimeout(() => {  
            // 假设操作成功  
            // resolve 调用会触发 then 方法中的第一个参数(onFulfilled)  
            resolve('Data fetched successfully!');  
  
            // 如果操作失败,我们会调用 reject  
            // reject('Failed to fetch data!');  
        }, 1000);  
    });  
}  
  
// 使用 then 方法处理 Promise 的结果  
fetchData()  
    .then(  
        // 第一个参数:处理成功情况的函数  
        (data) => {  
            console.log(data); // 输出 'Data fetched successfully!'  
  
            // 你可以在这里进行其他操作,或者返回一个新的值或 Promise  
            // return someOtherAsyncFunction();  
        },  
  
        // 第二个参数:处理失败情况的函数  
        (error) => {  
            console.error(error); // 如果 fetchData 中的 Promise 被 reject,则会输出错误信息  
        }  
    )  
    .catch((error) => {  
        // 注意:catch 方法实际上是 then(undefined, onRejected) 的简写  
        // 所以如果 Promise 在上述 then 的第一个参数中出错,并且没有返回一个新的 Promise,  
        // 那么错误会被传递到 catch 方法中  
        console.error('An error occurred:', error);  
    });

示例代码具体说明:

定义了一个 fetchData 函数,它返回一个 Promise。在 Promise 的执行器中,我们使用 setTimeout 模拟异步操作。如果操作成功,我们调用 resolve 方法并传递一个字符串。如果操作失败,我们会调用 reject 方法并传递一个错误信息(但在上面的示例中,我们注释掉了 reject 调用以便看到成功处理的情况)。

然后使用 then 方法处理 fetchData 返回的 Promise。我们传递了两个参数:一个用于处理成功情况的函数和一个用于处理失败情况的函数。在成功处理函数中,我们简单地打印出从 Promise 接收到的数据。在失败处理函数中,我们打印出错误信息。

最后的catch 方法来捕获任何在 Promise 链中未处理的错误,它可以捕获在 then 方法中发生的任何错误(如果 then 的第一个参数中的代码抛出错误,并且没有返回一个新的 Promise 来处理该错误)。


then只填写一个参数

如果只填写一个参数,那么这个参数就是解决处理函数(fulfilled handler)。这意味着你只对Promise成功解决(fulfilled)的情况感兴趣,并希望在该情况下执行某个操作。

当只提供一个参数给then方法时,如果之后需要处理Promise拒绝(rejected)的情况,需要在链中的后续then调用中提供拒绝处理函数,或者使用catch方法来捕获和处理任何错误。

这里有一个例子来说明这一点:

// 假设有一个返回Promise的函数  
function fetchData() {  
  return new Promise((resolve, reject) => {  
    // 模拟异步操作  
    setTimeout(() => {  
      // 假设请求成功,解决Promise  
      if (Math.random() > 0.5) {  
        resolve('成功获取的数据');  
      } else {  
        // 假设请求失败,拒绝Promise  
        reject(new Error('请求失败'));  
      }  
    }, 1000);  
  });  
}  
  
// 使用then,只提供一个参数来处理成功的情况  
fetchData()  
  .then(data => {  
    // 这个函数只会在Promise解决时被调用  
    console.log('数据获取成功:', data);  
    // 注意:这里没有处理拒绝的情况  
  })  
  .catch(error => {  
    // 这个函数会捕获链中任何位置发生的错误  
    console.error('发生错误:', error);  
  });

then的链式调用

then 方法本身也返回一个新的 Promise,这允许你进行链式调用。你可以继续调用返回的 Promise 的 then 方法,来处理后续的操作。

then链式调用的好处

  • 错误冒泡:如果在Promise链的某个环节抛出了错误,并且没有在该环节捕获它,那么这个错误会“冒泡”到链中的下一个.catch块。这允许你在链的末尾捕获并处理所有可能的错误,而不需要在链的每一个环节都添加错误处理代码。

  • 代码组织then链允许你以一种直观的方式组织异步代码。你可以按照逻辑顺序将多个异步操作串联起来,每个操作都依赖于前一个操作的完成。这使得异步代码看起来像同步代码一样,更容易理解和跟踪。

  • 可读性:通过链式调用,你可以将每个异步步骤的意图明确地表达出来。每个then块都负责处理上一步的结果,并可能产生新的结果供下一步使用。这种明确的职责划分使得代码更加清晰和易于理解。

  • 可维护性:由于Promise链中的每个步骤都是独立的,因此你可以轻松地添加、删除或修改某个步骤,而不会影响其他步骤。这种模块化的设计使得代码更加可维护。

  • 避免回调地狱(Callback Hell):传统的回调函数嵌套方式容易导致代码难以阅读和维护,特别是当有多层嵌套时。Promise通过链式调用提供了一种更优雅的方式来处理异步操作,避免了回调地狱的问题。

  • 中间值传递:在Promise链中,每个then都可以接收前一个then的返回值作为参数。这使得在多个步骤之间传递中间值变得非常简单和直观。

下面是一个使用 Promise 的 then 链式调用的示例:

// 假设有一个返回 Promise 的函数  
function fetchData() {  
  return new Promise((resolve, reject) => {  
    // 模拟异步操作,比如发送网络请求  
    setTimeout(() => {  
      // 假设请求成功,解决 Promise  
      resolve('成功获取的数据');  
      // 如果请求失败,则应该调用 reject('失败原因')  
    }, 1000);  
  });  
}  
  
// 使用 then 链式调用来处理异步操作的结果  
fetchData()  
  .then(data => {  
    // 第一个 then,处理成功获取的数据  
    console.log('第一步处理:', data);  
    // 对数据进行一些处理,然后返回新的值或另一个 Promise  
    return data.toUpperCase(); // 假设我们只是简单地将数据转换为大写  
  })  
  .then(transformedData => {  
    // 第二个 then,处理转换后的数据  
    console.log('第二步处理(转换后):', transformedData);  
    // 在这里可以继续处理数据,或者返回另一个 Promise 进行更多的异步操作  
  })  
  .catch(error => {  
    // catch 用于捕获链中任何位置发生的错误  
    console.error('发生错误:', error);  
    // 在这里处理错误,或者进行错误恢复等操作  
  });

PS:每个 then 方法都返回一个新的 Promise,因此你可以继续链式调用 then 来处理更多的异步操作。如果你想要在某个点停止链式调用并处理可能出现的错误,你可以在该点调用 catch 方法。


catch()

catch 方法实际上是 then(undefined, onRejected) 的简写,所以它可以捕获在 then 方法中发生的任何错误(如果 then 的第一个参数中的代码抛出错误,并且没有返回一个新的 Promise 来处理该错误)。

优点

  • 代码清晰:错误处理代码与业务逻辑代码分开,使得代码更易于阅读和维护。

  • 无需编写重复代码:避免在多个地方重复编写相同的错误处理代码。

  • 错误冒泡:一旦在 Promise 链中的某个环节发生错误,这个错误会“冒泡”到最近的 catch 方法,这样你就不必担心错误在链中丢失或被忽略。

如果使用then方法的第二个参数处理错误,catch方法不会被执行

示例:

const promise = new Promise((resolve, reject) => {  
  reject(new Error('Something went wrong!'));  
});  
  
promise.then(  
  result => {  
    console.log('Fulfilled:', result);  
  },  
  error => {  
    console.error('Caught in then:', error.message);  // 输出"Caught in then: Something went wrong!"
    // 如果这里不抛出新的错误,那么catch将不会被调用  
  }  
)  
.catch(error => {  
  console.error('Caught in catch:', error.message);  
  // 这个catch将不会被调用,因为then的错误处理函数已经处理了错误  
});

如果使用then方法的第二个参数处理错误,但是在处理错误抛出了新的错误,那么catch方法将会被调用,以捕获这个新的错误。

示例:

const promise = new Promise((resolve, reject) => {  
  reject(new Error('Something went wrong!'));  
});  
  
promise.then(  
  result => {  
    console.log('Fulfilled:', result);  
  },  
  error => {  
    console.error('Caught in then:', error.message);  // 输出"Caught in then: Something went wrong!"
    throw new Error('Another error occurred!'); // 抛出新的错误  
  }  
)  
.catch(error => {  
  console.error('Caught in catch:', error.message);  // 输出"Caught in catch: Another error occurred!"
  // 这个catch将会被调用,因为then的错误处理函数抛出了新的错误  
});

错误集中处理

在Promise链中的某个环节(即某个.then回调中)抛出了一个错误,并且该环节没有通过提供第二个参数(拒绝处理函数)来捕获这个错误,那么这个错误将会沿着链向后传递,直到遇到第一个 .catch 方法。

可以将错误处理代码放在.catch块中,.catch方法会捕获这个错误,并执行相应的错误处理代码。这样可以将错误处理代码集中放置在链的末尾,避免在每个.then回调中都编写错误处理逻辑。这种集中处理的方式使得代码更加简洁和易于维护

示例1:

Promise.resolve()  
  .then(() => {  
    console.log('第一个then');  
    throw new Error('出错了!'); // 抛出一个错误  
  })  
  .then(() => {  
    console.log('第二个then(这个不会执行)');  
  }, error => {  
    // 这里的拒绝处理函数不会执行,因为错误是在前一个then中抛出的  
    // 并且它没有被传递到当前then的拒绝处理函数中  
    console.error('这个错误处理函数不会执行:', error.message);  
  })  
  .catch(error => {  
    // 错误会被传递到这里的catch中,并输出错误信息  
    console.error('捕获到错误:', error.message);  
  });

示例2:

Promise.resolve(1)  
  .then(value => {  
    console.log('第一个then:', value);  
    // 模拟一个错误  
    throw new Error('出错了!');  
  })  
  .then(value => {  
    // 这个then不会被执行,因为前面的then抛出了错误  
    console.log('第二个then(这个不会执行):', value);  
  })  
  .catch(error => {  
    // 捕获并处理错误  
    console.error('捕获到错误:', error.message);  
  });

错误传递

Promise的 .then 链在发现错误时,会立即将该错误传递给后续的 .catch 方法(如果存在),而不是等待整个链中的所有 .then 都执行完。这是Promise错误处理机制的一个重要特性,它允许你在错误发生的那一刻就立即开始处理它,而不是等到整个异步操作序列完成。

Promise.resolve()  
  .then(() => {  
    console.log('第一个then');  
    throw new Error('出错了!');  
  })  
  .then(() => {  
    console.log('第二个then(这个不会执行)');  
  })  
  .catch(error => {  
    console.error('捕获到错误:', error.message);  
  });

finally()

特性

  • finally() 方法返回一个原生的 Promise,在 Promise 链完成时,无论 Promise 是被解决还是被拒绝,都会执行的代码。这对于清理操作(如关闭数据库连接、移除事件监听器等)非常有用,因为无论 Promise 的结果如何,这些操作都需要执行。
  • 可以在 finally() 之后继续链式调用 .then() 或 .catch(),这些调用将基于原始 Promise 的状态(解决或拒绝)来执行。然而,finally() 方法内部的返回值或抛出的错误确实会被忽略,不会影响到 finally() 之后链式调用的 Promise 处理。

示例1:

function fetchData() {  
  return new Promise((resolve, reject) => {  
    // 假设这是一个异步操作,例如从服务器获取数据  
    setTimeout(() => {  
      // 模拟成功获取数据  
      resolve('Data fetched successfully!');  
    }, 2000);  
  
    // 注意:在实际应用中,你通常不会在这里同时调用 resolve 和 reject  
    // 这只是为了演示目的  
    // setTimeout(() => {  
    //   // 模拟失败获取数据  
    //   reject('Failed to fetch data!');  
    // }, 2000);  
  });  
}  
  
fetchData()  
  .then(data => {  
    console.log(data); // 'Data fetched successfully!'  
  })  
  .catch(error => {  
    console.error(error); // 如果 fetchData 失败,则显示错误信息  
  })  
  .finally(() => {  
    // 无论 fetchData 成功还是失败,都会执行这里的代码  
    console.log('Cleaning up resources...');  
  });

示例2:

function fetchData() {  
  return new Promise((resolve, reject) => {  
    // 模拟异步操作  
    setTimeout(() => {  
      // 模拟成功获取数据  
      resolve('Data fetched successfully!');  
    }, 2000);  
  });  
}  
  
fetchData()  
  .then(data => {  
    console.log(data); // 'Data fetched successfully!'  
    return data; // 返回数据给下一个 .then()  
  })  
  .catch(error => {  
    console.error(error); // 如果 fetchData 失败,则显示错误信息  
    throw error; // 重新抛出错误给下一个 .catch()  
  })  
  .finally(() => {  
    // 无论 fetchData 成功还是失败,都会执行这里的代码  
    console.log('Cleaning up resources...');  
    // 这里返回的值或抛出的错误都会被忽略  
    return 'This will be ignored'; // 返回值被忽略  
    // throw new Error('This error will also be ignored'); // 抛出的错误也会被忽略  
  })  
  .then(data => {  
    // 这个 .then() 将会接收到 fetchData() 解决时返回的数据  
    // 因为 finally() 内部的返回值被忽略了  
    console.log('After finally:', data); // 'After finally: Data fetched successfully!'  
  })  
  .catch(error => {  
    // 如果 fetchData() 失败,并且错误没有被之前的 .catch() 捕获,  
    // 则这个 .catch() 将会捕获到错误  
    console.error('Unhandled error:', error);  
  });

PS:.finally() 方法会在 Promise 链的末尾执行,无论前面的 .then() 或 .catch() 是否捕获到了错误。这通常用于执行一些清理工作,比如关闭资源、更新 UI 以反映请求已完成等。注意,.finally() 方法内部的代码不会改变 Promise 的结果(即它不会返回一个新的值或抛出一个新的错误),它只会确保某些代码块总是会被执行。


all()

是一个静态方法,用于处理一个 Promise 对象组成的数组,并返回一个新的 Promise 实例。这个新的 Promise 会在所有给定的 Promise 都成功解决后才会解决,并将这些 Promise 的结果作为一个数组返回。如果任何一个 Promise 被拒绝,则返回的 Promise 会立即被拒绝,并返回第一个被拒绝的 Promise 的结果。

function fetchData1() {  
  return new Promise((resolve, reject) => {  
    setTimeout(() => {  
      resolve('Data 1 fetched successfully!');  
    }, 2000);  
  });  
}  
  
function fetchData2() {  
  return new Promise((resolve, reject) => {  
    setTimeout(() => {  
      resolve('Data 2 fetched successfully!');  
    }, 1000);  
  });  
}  
  
Promise.all([fetchData1(), fetchData2()])  
  .then((results) => {  
    console.log(results); // 输出: ['Data 1 fetched successfully!', 'Data 2 fetched successfully!']  
    // 这里 results 是一个数组,包含了两个 Promise 的解决值  
  })  
  .catch((error) => {  
    console.error('An error occurred:', error);  
    // 如果有任何一个 Promise 被拒绝,这里会捕获到第一个被拒绝的 Promise 的错误  
  });  
  
// 注意:Promise.all 等待的是最慢的那个 Promise 完成  
// 在这个例子中,尽管 fetchData2 比 fetchData1 更快解决,但 Promise.all 会在两者都解决后才解决

race()

是一个静态方法,它接受一个 Promise 对象的数组,并返回一个新的 Promise 实例。与 Promise.all() 不同的是,Promise.race() 会在数组中的第一个 Promise 被解决(fulfilled)或拒绝(rejected)时,立即返回一个新的 Promise,并传递那个 Promise 的结果。

如果你有一组异步操作,并且你只关心第一个完成的操作(无论它是成功还是失败),那么 Promise.race() 就是一个很好的选择。

示例:

function fetchData1() {  
  return new Promise((resolve, reject) => {  
    setTimeout(() => {  
      resolve('Data 1 fetched successfully!');  
    }, 2000);  
  });  
}  
  
function fetchData2() {  
  return new Promise((resolve, reject) => {  
    setTimeout(() => {  
      resolve('Data 2 fetched successfully!');  
    }, 1000);  
  });  
}  
  
Promise.race([fetchData1(), fetchData2()])  
  .then((result) => {  
    console.log(result); // 输出: 'Data 2 fetched successfully!'(因为 fetchData2 更快)  
    // 这里 result 是第一个被解决的 Promise 的结果  
  })  
  .catch((error) => {  
    console.error('An error occurred:', error);  
    // 如果有任何一个 Promise 被拒绝,并且没有其他 Promise 被解决,则这里会捕获到第一个被拒绝的 Promise 的错误  
  });  
  
// 注意:Promise.race() 只关心第一个完成的 Promise  
// 在这个例子中,尽管 fetchData1 和 fetchData2 都会解决,但 Promise.race 会在 fetchData2 解决后立即解决

PS:Promise.race() 是另一个处理多个 Promise 的方法。与 Promise.all() 不同,它不会等待所有 Promise 都完成。它会立即开始监听 fetchPromises 数组中的所有 Promise。当数组中的任何一个 Promise 变为 fulfilled 或 rejected 时,Promise.race() 就会以相同的值或原因立即解决或拒绝。这意味着即使其他请求可能仍在后台进行,Promise.race() 也只会关心第一个成功或失败的请求。


resolve()和reject()

都是静态方法,Promise 构造函数接受一个执行器函数作为参数,这个执行器函数有两个参数:resolve 和 reject。它们是预定义的函数,用于改变 Promise 的状态。

  • Promise.resolve() 返回一个状态为已解决的 Promise 对象,并将对象作为解决值进行传递。
  • Promise.reject() 返回一个状态为已拒绝的 Promise 对象,并将对象作为拒绝原因进行传递。

示例1:

// 基本用法
// 使用 Promise.resolve()  
Promise.resolve('Hello, World!')  
  .then(result => console.log(result)); // 输出 "Hello, World!"  
  
// 使用 Promise.reject()  
Promise.reject(new Error('Something went wrong'))  
  .catch(error => console.error(error.message)); // 输出 "Something went wrong"

示例2:

// 创建一个新的 Promise  
const promise = new Promise((resolve, reject) => {  
  // 模拟异步操作  
  setTimeout(() => {  
    // 假设异步操作成功  
    const successValue = 'Async operation succeeded!';  
    // 使用 resolve 方法将 Promise 标记为已解决,并传递一个值  
    resolve(successValue);  
  }, 1000);  
  
  // 注意:在实际应用中,你可能还需要处理错误情况  
  // 如果异步操作失败,你可以使用 reject 方法将 Promise 标记为已拒绝,并传递一个错误对象  
  // 例如:  
  // if (/* some error condition */) {  
  //   const error = new Error('Async operation failed');  
  //   reject(error);  
  // }  
});  
  
// 使用 .then() 处理 Promise 解决的情况  
promise.then((value) => {  
  console.log(value); // 输出: 'Async operation succeeded!'  
}).catch((error) => {  
  console.error('An error occurred:', error);  
  // 如果 Promise 被拒绝,这里的 catch 会捕获到错误并处理它  
});

PS:一旦 Promise 被解决或拒绝,它的状态就不能再改变。也就是说,你不能在一个已经解决的 Promise 上再调用 reject(),也不能在一个已经拒绝的 Promise 上再调用 resolve()。


Promise对象的具体使用

Promise 对象本身并不能直接发起网络请求Promise 是一个表示异步操作最终完成或失败的对象,它通常与异步函数或方法(如 Fetch API、XMLHttpRequest、axios 等)一起使用,以处理这些异步操作的结果。

以下是一个Fetch API发起请求并使用Promise对象的示例:

// 使用 fetch API 发起 GET 请求  
fetch('https://api.example.com/data')  
    .then(response => {  
        // 检查响应是否成功(状态码 200-299)  
        if (!response.ok) {  
            throw new Error('Network response was not ok');  
        }  
        // 解析 JSON 响应体  
        return response.json();  
    })  
    .then(data => {  
        // 请求成功,处理返回的数据  
        console.log('Data fetched successfully:', data);  
    })  
    .catch(error => {  
        // 请求失败或解析 JSON 失败,处理错误  
        console.error('Error fetching data:', error);  
    });

以下是一个axios发起请求并使用Promise对象的示例:

// 假设你已经安装了 axios  
const axios = require('axios');  
  
// 使用 axios 发起 GET 请求  
axios.get('https://api.example.com/data')  
    .then(response => {  
        // 请求成功,处理返回的数据  
        console.log('Data fetched successfully:', response.data);  
    })  
    .catch(error => {  
        // 请求失败,处理错误  
        console.error('Error fetching data:', error);  
    });

以下是一个XMLHttpRequest发起请求并使用Promise对象的示例:

虽然XMLHttpRequest是一个较老的API,但它仍然可以被用来发起网络请求。然而,XMLHttpRequest本身并不返回Promise对象。但我们可以手动创建一个Promise来封装XMLHttpRequest的异步行为。

function xhrPromise(url, method = 'GET') {  
    // 返回一个Promise对象  
    return new Promise((resolve, reject) => {  
        // 创建一个新的XMLHttpRequest对象  
        const xhr = new XMLHttpRequest();  
  
        // 设置请求的类型、URL等  
        xhr.open(method, url, true);  
  
        // 定义请求完成时的回调函数  
        xhr.onload = function () {  
            // 请求成功(状态码在200-299之间)  
            if (xhr.status >= 200 && xhr.status < 300) {  
                // 解析响应体(这里假设是JSON格式)  
                try {  
                    const response = JSON.parse(xhr.responseText);  
                    // 解析成功,resolve响应数据  
                    resolve(response);  
                } catch (e) {  
                    // 解析失败,reject错误  
                    reject(e);  
                }  
            } else {  
                // 请求失败(状态码不在200-299之间)  
                reject(new Error(`HTTP error! status: ${xhr.status}`));  
            }  
        };  
  
        // 定义网络错误时的回调函数  
        xhr.onerror = function () {  
            // 网络错误,reject错误  
            reject(new Error('Network Error'));  
        };  
  
        // 发送请求  
        xhr.send();  
    });  
}  
  
// 使用示例  
xhrPromise('https://api.example.com/data')  
    .then(data => {  
        // 请求成功,处理返回的数据  
        console.log('Data fetched successfully:', data);  
    })  
    .catch(error => {  
        // 请求失败或解析失败,处理错误  
        console.error('Error fetching data:', error);  
    });

PS:Promise 对象本身不发起请求,但它用于处理由其他异步操作(如网络请求)产生的异步结果。


async/await 语法

async/await 是基于 Promise 的一种更直观的异步编程语法。async 关键字用于声明一个函数是异步的,而 await 关键字用于在异步函数内部等待一个 Promise。这使得异步代码看起来更像同步代码,提高了代码的可读性和可维护性。

示例:

// 使用async关键字声明一个异步函数  
async function fetchData() {  
  try {  
    // 发起一个异步操作,如使用fetch API  
    const response = await fetch('https://api.example.com/data');  
    // 确保响应成功  
    if (!response.ok) {  
      throw new Error(`HTTP error! status: ${response.status}`);  
    }  
    // 解析响应数据  
    const data = await response.json();  
    // 返回解析后的数据  
    return data;  
  } catch (error) {  
    // 捕获并处理错误  
    console.error('There has been a problem with your fetch operation:', error);  
    // 通常这里也会返回一个错误,以便在调用处可以处理  
    throw error;  
  }  
}  
  
// 调用异步函数  
fetchData().then(data => {  
  // 处理返回的数据  
  console.log(data);  
}).catch(error => {  
  // 处理可能发生的错误  
  console.error('Error fetching data:', error);  
});  
  
// 或者使用async/await在另一个异步函数中调用  
async function useData() {  
  try {  
    const data = await fetchData(); // 等待fetchData完成并返回数据  
    // 现在你可以使用data了  
    console.log('Data fetched and used:', data);  
  } catch (error) {  
    // 处理错误  
    console.error('Error using the fetched data:', error);  
  }  
}  
  
// 调用useData函数  
useData();

PS:async关键字是用来声明一个函数是异步的,它必须写在函数声明或函数表达式上。当你使用async关键字声明一个函数时,这个函数就会返回一个Promise对象。即使该函数内部没有显式地返回一个新的Promise,JavaScript引擎也会自动将其转换为Promise


相关疑问

回调地狱

当使用传统的 AJAX 调用和回调函数时,如果多个异步操作需要按顺序执行,并且每个操作都依赖于前一个操作的结果,那么代码可能会变得嵌套很深,形成所谓的“回调地狱”(Callback Hell)。以下是一个使用 AJAX 造成回调地狱的例子:

// 假设有三个嵌套的 AJAX 请求,每个都依赖于前一个的结果  
function fetchDataWithCallbacks() {  
    // 第一个 AJAX 请求  
    $.ajax({  
        url: 'https://api.example.com/data1',  
        success: function(data1) {  
            console.log('Received data1:', data1);  
              
            // 第二个 AJAX 请求,依赖于第一个请求的结果  
            $.ajax({  
                url: 'https://api.example.com/data2?param=' + data1.someParam,  
                success: function(data2) {  
                    console.log('Received data2:', data2);  
                      
                    // 第三个 AJAX 请求,依赖于第二个请求的结果  
                    $.ajax({  
                        url: 'https://api.example.com/data3?param=' + data2.anotherParam,  
                        success: function(data3) {  
                            console.log('Received data3:', data3);  
                              
                            // 处理最终的数据  
                            processFinalData(data3);  
                        },  
                        error: function(jqXHR, textStatus, errorThrown) {  
                            console.error('Error fetching data3:', textStatus, errorThrown);  
                        }  
                    });  
                },  
                error: function(jqXHR, textStatus, errorThrown) {  
                    console.error('Error fetching data2:', textStatus, errorThrown);  
                }  
            });  
        },  
        error: function(jqXHR, textStatus, errorThrown) {  
            console.error('Error fetching data1:', textStatus, errorThrown);  
        }  
    });  
}  
  
// 处理最终数据的函数  
function processFinalData(data) {  
    // 处理数据的逻辑...  
    console.log('Final data processed:', data);  
}  
  
// 调用函数开始处理  
fetchDataWithCallbacks();

示例中每个 AJAX 请求都嵌套在另一个请求的 success 回调函数中,这导致代码的可读性和可维护性降低。


Promise对象解决回调地狱的链式调用方案

当使用Promise对象来处理链式调用(多层嵌套)的异步请求时,一种常见的做法是使用.then()链式调用。但是,随着嵌套层级的增加,代码的可读性和可维护性会下降。为了避免这种情况,我们可以使用Promise的async/await语法糖,它可以使异步代码看起来更像同步代码。

function fetchDataFirst() {  
    return new Promise((resolve, reject) => {  
        // 假设这里是一个异步请求  
        setTimeout(() => {  
            resolve('First data');  
        }, 1000);  
    });  
}  
  
function fetchDataSecond(firstData) {  
    return new Promise((resolve, reject) => {  
        // 假设这里依赖第一个请求的结果  
        setTimeout(() => {  
            resolve(`Second data based on ${firstData}`);  
        }, 1000);  
    });  
}  
  
function fetchDataThird(secondData) {  
    return new Promise((resolve, reject) => {  
        // 假设这里依赖第二个请求的结果  
        setTimeout(() => {  
            resolve(`Third data based on ${secondData}`);  
        }, 1000);  
    });  
}  
  
fetchDataFirst()  
    .then(firstData => fetchDataSecond(firstData))  
    .then(secondData => fetchDataThird(secondData))  
    .then(thirdData => {  
        console.log(thirdData); // 输出:Third data based on Second data based on First data  
    })  
    .catch(error => {  
        console.error('An error occurred:', error);  
    });

/*

使用async/await可以使代码更加简洁和直观,因为它避免了深度的嵌套,并且使得异步代码看起来更像同步代码。
async function fetchAllData() {  
    try {  
        const firstData = await fetchDataFirst();  
        const secondData = await fetchDataSecond(firstData);  
        const thirdData = await fetchDataThird(secondData);  
        console.log(thirdData); // 输出:Third data based on Second data based on First data  
    } catch (error) {  
        console.error('An error occurred:', error);  
    }  
}  
  
fetchAllData();

*/

只有异步请求才能使用Promise对象吗

不是异步请求也可以使用Promise,虽然Promise对象在异步编程中特别有用,尤其是用于处理异步请求的结果,但它们也可以用于其他类型的异步操作。

Promise是一个代表异步操作最终完成或失败的对象。这意味着你可以使用Promise来封装任何异步操作,而不仅仅是网络请求。以下是一些可以使用Promise的异步操作的例子:

定时器(setTimeout/setInterval)
虽然setTimeout和setInterval不是基于Promise的API,但你可以很容易地将它们转换为返回Promise的函数。

function delay(ms) {  
    return new Promise(resolve => setTimeout(resolve, ms));  
}  
  
delay(1000).then(() => console.log('One second passed.'));

同步请求

通常的同步操作(即阻塞主线程的操作)不需要使用Promise,因为它们是立即完成的。如果使用Promise对象实际上并没有带来任何异步的好处。在大多数情况下,对于真正的同步操作,直接使用返回值而不是Promise是更加高效和简单的做法。

PS:Promise是一个强大的工具,可以用于处理任何类型的异步操作,而不仅仅是网络请求。


为什么Fetch API和axios请求会返回Promise对象

因为它们都是基于Promise的HTTP客户端。Promise在JavaScript中提供了一种处理异步操作的标准方式,使得代码的组织更加清晰,也更容易处理异步操作的结果或错误。


所有的异步请求都会返回Promise对象吗

是否返回Promise对象取决于你使用的具体API或库。现代的异步编程API和库通常都会返回Promise对象,因为它们提供了一种更加简洁和易于处理异步操作的方式。

异步请求本身并不默认返回Promise对象。然而,许多现代的异步编程API和库,如fetchaxios、或其他基于Promise的HTTP客户端,确实会返回Promise对象来表示异步操作的结果。

Promise是ES6引入的一种表示异步操作最终完成(或失败)及其结果值的对象。当你使用这些返回Promise的API时,你可以使用.then().catch()方法来处理异步操作的结果或错误。

但是,并不是所有的异步请求或操作都会返回Promise对象。例如,传统的XMLHttpRequest对象就不使用Promise,而是使用回调函数来处理异步操作的结果。然而,你可以手动创建一个Promise来封装XMLHttpRequest的异步行为。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值