Promise

Promise

promise

JavaScript 中的 Promise 是一种用于异步编程的对象,它代表了一个异步操作的最终完成(或失败)及其结果值。Promise 有三种状态:

  1. Pending(等待中):初始状态,既不是成功,也不是失败状态。
  2. Fulfilled(已成功):意味着操作成功完成。
  3. Rejected(已失败):意味着操作失败。
    一个 Promise 对象有以下几个方法:
  • Promise.prototype.then():在 Promise 成功时执行,可以链式调用。
  • Promise.prototype.catch():在 Promise 失败时执行,用于错误处理。
  • Promise.prototype.finally():无论 Promise 成功还是失败,都会执行。

创建 Promise

创建一个 Promise 对象通常使用 new Promise 构造函数,并提供一个执行器函数(executor function),它将在 Promise 创建后立即执行:

1
2
3
4
5
6
7
8
let myPromise = new Promise((resolve, reject) => {
// 异步操作
if (/* 异步操作成功 */) {
resolve(value); // 将 Promise 状态设置为成功,并返回 value
} else {
reject(error); // 将 Promise 状态设置为失败,并返回 error
}
});

使用 Promise

1
2
3
4
5
6
7
myPromise.then((value) => {
// 当 Promise 成功时,这里的代码将被执行
}).catch((error) => {
// 当 Promise 失败时,这里的代码将被执行
}).finally(() => {
// 不管 Promise 成功还是失败,这里的代码都将被执行
});

Promise 链

Promise 可以被链式调用,因为 then()catch() 方法都会返回一个新的 Promise 对象:

1
2
3
4
5
6
7
8
9
10
11
myPromise
.then((value) => {
// 处理第一个异步操作的结果
return anotherAsyncOperation(value);
})
.then((result) => {
// 处理第二个异步操作的结果
})
.catch((error) => {
// 处理所有 Promise 链中的错误
});

Promise.all()

Promise.all() 方法用于处理多个 Promise 对象,并且只有当所有 Promise 都成功时,才会返回结果:

1
2
3
4
5
6
7
Promise.all([promise1, promise2, promise3])
.then(([result1, result2, result3]) => {
// 所有 Promise 都成功时,这里的代码将被执行
})
.catch((error) => {
// 任何一个 Promise 失败时,这里的代码将被执行
});

Promise.race()

Promise.race() 方法同样用于处理多个 Promise 对象,但是只要有一个 Promise 完成(无论是成功还是失败),就会立即返回结果:

1
2
3
4
5
6
7
Promise.race([promise1, promise2, promise3])
.then((value) => {
// 第一个完成的 Promise 的结果将被返回
})
.catch((error) => {
// 第一个完成的 Promise 的错误将被返回
});

Promise 是 JavaScript 异步编程的基础,提供了一种更加清晰和可控的方式来处理异步操作。

使用场景

JavaScript 中的 Promise 被广泛用于处理异步操作,以下是一些常见的使用场景:

  1. 异步请求(如 AJAX)
    Promise 可以用于处理 HTTP 请求,例如使用 fetch API 时,fetch 本身返回一个 Promise 对象,可以链式调用 thencatch 来处理请求成功和失败的情况。
    1
    2
    3
    4
    fetch('https://api.example.com/data')
    .then(response => response.json())
    .then(data => console.log(data))
    .catch(error => console.error('Error:', error));
  2. 定时操作(如 setTimeout)
    虽然 setTimeout 本身不是基于 Promise 的,但你可以创建一个返回 Promise 的函数来模拟异步延迟。
    1
    2
    3
    4
    function delay(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
    }
    delay(1000).then(() => console.log('1 second passed'));
  3. 文件读取和写入
    在 Node.js 中,fs 模块提供了基于 Promise 的 API(例如 fs.promises),允许你以异步方式读取和写入文件。
    1
    2
    3
    4
    const fs = require('fs').promises;
    fs.readFile('file.txt', 'utf8')
    .then(data => console.log(data))
    .catch(err => console.error(err));
  4. 数据库操作
    许多数据库库提供了基于 Promise 的 API,使得你可以以异步方式查询和操作数据库。
    1
    2
    3
    database.query('SELECT * FROM users')
    .then(users => console.log(users))
    .catch(error => console.error(error));
  5. 并行执行多个异步操作
    使用 Promise.all 可以同时执行多个异步操作,并在所有操作都成功完成时返回结果。
    1
    2
    3
    4
    5
    6
    7
    Promise.all([fetch(url1), fetch(url2)])
    .then(responses => Promise.all(responses.map(res => res.json())))
    .then(data => {
    console.log(data[0]); // 第一个请求的结果
    console.log(data[1]); // 第二个请求的结果
    })
    .catch(error => console.error(error));
  6. 串行执行多个异步操作
    通过链式调用 then 方法,可以确保异步操作按顺序执行。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    fetch(url1)
    .then(response1 => response1.json())
    .then(data1 => {
    return fetch(url2);
    })
    .then(response2 => response2.json())
    .then(data2 => {
    console.log(data1, data2);
    })
    .catch(error => console.error(error));
  7. 错误处理
    Promisecatch 方法提供了一个集中处理异步操作中出现错误的地方。
    1
    2
    3
    4
    5
    6
    fetch(url)
    .then(response => response.json())
    .catch(error => {
    // 处理请求或解析过程中的错误
    console.error('Failed to fetch:', error);
    });
  8. 动态加载模块
    在支持动态 import() 的环境中,import() 返回一个 Promise,可以用来按需加载 JavaScript 模块。
    1
    2
    3
    4
    5
    import(/* webpackChunkName: "feature-module" */ './feature-module')
    .then(module => {
    module.loadFeature();
    })
    .catch(error => console.error('Module loading failed:', error));
  9. 事件监听
    虽然不是直接使用 Promise,但可以通过封装事件监听器来返回 Promise,使得可以以异步方式等待某个事件的发生。
    1
    2
    3
    4
    5
    6
    function waitForEvent(eventType) {
    return new Promise(resolve => {
    document.addEventListener(eventType, resolve, { once: true });
    });
    }
    waitForEvent('click').then(event => console.log('Clicked!', event));
    Promise 提供了一种强大的方式来处理异步操作,使得代码更加清晰和易于维护。

Promise 和 async/await 有什么区别?

async和await
Promiseasync/await 都是JavaScript中处理异步操作的工具,但它们在语法和使用上有所不同。以下是它们之间的一些主要区别:

  1. 语法和可读性
    • Promise:使用 .then().catch() 方法来处理异步操作的结果和错误。代码可能看起来像“回调地狱”(callback hell),尤其是在多个异步操作嵌套时。
    • async/await:提供了一种更接近同步代码的写法,使得异步代码更易于阅读和维护。async 关键字用于声明一个函数是异步的,而 await 关键字用于等待一个 Promise 解决。
  2. 错误处理
    • Promise:错误通过 .catch() 方法捕获,这要求每个异步操作都必须被包裹在 try/catch 块中,或者链式调用 .catch()
    • async/await:错误可以通过传统的 try/catch 块捕获,这使得错误处理更直观和一致。
  3. 控制流
    • Promise:需要链式调用 .then() 方法来处理后续的异步操作,这可能导致代码难以跟踪。
    • async/await:允许你以线性方式编写异步代码,类似于同步代码,使得控制流更清晰。
  4. 性能
    • Promise:在某些情况下,Promise 可能比 async/await 稍微快一些,因为 async/await 实际上是基于 Promise 实现的。
    • async/await:虽然它提供了更好的语法糖,但在底层,async/await 仍然依赖于 Promise,因此性能差异通常可以忽略不计。
  5. 使用场景
    • Promise:适用于需要链式调用多个异步操作的场景,或者当你需要在异步操作完成后执行多个回调函数时。
    • async/await:适用于需要等待单个异步操作完成的场景,或者当你想要以同步的方式编写异步代码时。
  6. 兼容性
    • Promise:在ES6中引入,已被广泛支持。
    • async/await:在ES2017中引入,兼容性较好,但可能在一些旧的JavaScript环境中不被支持。
  7. 代码简洁性
    • Promise:在处理多个异步操作时,代码可能会变得复杂和冗长。
    • async/await:通常可以减少代码量,使其更加简洁和易于理解。
      总的来说,async/await 是建立在 Promise 之上的语法糖,它提供了一种更简洁、更直观的方式来处理异步操作。然而,理解 Promise 的工作原理对于深入理解 async/await 以及处理更复杂的异步逻辑是非常重要的。

fetch

fetch 是 JavaScript 中用于发起网络请求的函数,它返回一个 Promise 对象,该对象在请求完成时解析为一个 Response 对象。fetch 提供了一种简单、现代的方式来处理 HTTP 请求和响应。下面是一些关于 fetch 的基本用法和示例:

基本用法

发起 GET 请求

1
2
3
4
5
6
7
8
9
10
11
12
13
fetch('https://api.example.com/data')
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json(); // 解析响应为 JSON
})
.then(data => {
console.log(data); // 处理数据
})
.catch(error => {
console.error('There was a problem with the fetch operation:', error);
});

在这个示例中,fetch 发起一个 GET 请求到指定的 URL。然后使用 .then() 方法处理响应。首先检查响应是否成功(response.ok),如果成功,则将响应解析为 JSON 格式。最后,处理解析后的数据或捕获并处理错误。

发起 POST 请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
fetch('https://api.example.com/data', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
key1: 'value1',
key2: 'value2'
})
})
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
console.log(data);
})
.catch(error => {
console.error('There was a problem with the fetch operation:', error);
});

在这个 POST 请求的示例中,我们设置了请求方法为 POST,并指定了请求头和请求体。请求体被设置为 JSON 字符串,因此需要在请求头中指定 Content-Typeapplication/json

使用 async/await

使用 asyncawait 可以使 fetch 请求的代码更加简洁和易于理解:

1
2
3
4
5
6
7
8
9
10
11
12
13
async function fetchData() {
try {
const response = await fetch('https://api.example.com/data');
if (!response.ok) {
throw new Error('Network response was not ok');
}
const data = await response.json();
console.log(data);
} catch (error) {
console.error('There was a problem with the fetchData operation:', error);
}
}
fetchData();

响应类型

  • .json():将响应体解析为 JSON 格式。
  • .text():将响应体解析为文本。
  • .blob():将响应体解析为 Blob 对象,适用于二进制数据。
  • .formData():将响应体解析为 FormData 对象,适用于表单数据。

错误处理

  • 网络错误:如果网络请求失败(例如,无法连接到服务器),fetch 会抛出一个异常,可以在 .catch() 中捕获。
  • HTTP 错误:如果服务器返回一个非 200 系列的状态码,fetch 仍然会解析为 Promise,但 response.ok 会为 false。需要手动检查 response.okresponse.status 来处理这种情况.

注意事项

  • CORS(跨源资源共享)fetch 请求可能会受到 CORS 策略的限制。如果请求的资源来自不同的源,服务器需要设置适当的 CORS 头以允许请求。
  • 请求超时fetch 本身没有内置的超时机制。如果需要实现超时功能,可以通过 Promise.race 或其他方法来实现.
  • 重试机制:在某些情况下,可能需要对失败的请求进行重试。可以编写一个重试逻辑来处理这种情况.
    fetch 是现代 Web 开发中常用的 API,它提供了一种简单而强大的方式来处理网络请求和响应。

async 和 await

async/await 是 JavaScript 中用于简化异步编程的两个关键字,它们基于 Promise 对象,使得异步代码的编写和阅读更接近同步代码的风格,从而提高代码的可读性和可维护性。

Async 关键字

  • async 关键字用于声明一个函数是异步的。当一个函数被声明为 async 时,它会隐式返回一个 Promise 对象。
  • async 函数内部可以包含 await 表达式,但只能包含一个 await 表达式。

Await 关键字

  • await 关键字用于等待一个 Promise 解决(resolve)或拒绝(reject)。
  • await 只能在 async 函数内部使用。
  • 当执行到 await 表达式时,JavaScript 运行时会暂停该 async 函数的执行,直到等待的 Promise 完成(解决或拒绝)。
  • 如果 Promise 成功解决,await 表达式的结果是解决的值;如果 Promise 被拒绝,await 表达式会抛出拒绝的原因。

使用示例

基本使用

1
2
3
4
5
6
7
8
9
async function fetchData() {
try {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
console.log(data);
} catch (error) {
console.error('Failed to fetch data:', error);
}
}

在这个例子中,fetchData 是一个 async 函数,它使用 await 等待 fetch 请求的结果。如果请求成功,它将解析 JSON 并打印数据。如果请求失败,它将捕获错误并打印错误信息。

错误处理

async/await 允许使用传统的 try/catch 块来处理错误,这使得错误处理更加直观。

1
2
3
4
5
6
7
8
9
10
11
12
async function fetchData() {
try {
const response = await fetch('https://api.example.com/data');
if (!response.ok) {
throw new Error('Network response was not ok');
}
const data = await response.json();
console.log(data);
} catch (error) {
console.error('Failed to fetch data:', error);
}
}

并行执行异步操作

虽然 async/await 使得代码看起来是同步的,但它仍然可以并行执行异步操作。

1
2
3
4
5
async function fetchMultipleUrls(urls) {
const promises = urls.map(url => fetch(url).then(res => res.json()));
const results = await Promise.all(promises);
console.log(results);
}

在这个例子中,fetchMultipleUrls 函数并行地对多个 URL 发起 fetch 请求,并等待所有请求完成。

注意事项

  • await 会暂停 async 函数的执行,直到等待的 Promise 解决。这可能会导致性能问题,特别是在 I/O 密集型操作中,因此应该谨慎使用。
  • 在循环中使用 await 会逐个等待每个迭代的 Promise,这可能不是你想要的行为,特别是在需要并行执行异步操作时。
    async/await 提供了一种更简洁和直观的方式来处理异步代码,使得异步编程更加接近同步编程的体验。

实例

  • then 有两个参数都是回调函数,第一个回调函数,在Promise  状态为 resolve 执行,第二个在状态为reject执行。catch方法,相当于then(null,reject)的一个变体。

前代的区别

Promise 是 JavaScript 中处理异步操作的一种更现代、更强大的方式,与之前的回调函数(Callback)和事件(Event)等异步处理方式相比,Promise 有以下几个显著的区别和优势:

一、回调函数(Callback)

  1. 定义
    • 回调函数是一种将函数作为参数传递给另一个函数,并在某个操作完成后调用该函数的方式。这是早期 JavaScript 中处理异步操作的主要方式。
    • 示例:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      function fetchData(callback) {
      setTimeout(() => {
      const data = '成功获取数据';
      callback(data);
      }, 1000);
      }
      fetchData((data) => {
      console.log(data); // 输出 '成功获取数据'
      });
  2. 问题
    • 回调地狱(Callback Hell):当多个异步操作需要顺序执行时,回调函数会嵌套多层,导致代码难以阅读和维护。
      1
      2
      3
      4
      5
      6
      7
      fetchData((data1) => {
      processData(data1, (data2) => {
      saveData(data2, (result) => {
      console.log(result);
      });
      });
      });
    • 错误处理困难:每个回调函数都需要单独处理错误,代码冗余且难以管理。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      fetchData((err, data1) => {
      if (err) {
      console.error(err);
      return;
      }
      processData(data1, (err, data2) => {
      if (err) {
      console.error(err);
      return;
      }
      saveData(data2, (err, result) => {
      if (err) {
      console.error(err);
      return;
      }
      console.log(result);
      });
      });
      });

二、事件(Event)

  1. 定义
    • 事件是一种基于事件驱动模型的异步处理方式。通过监听事件,当事件发生时执行特定的处理函数。
    • 示例:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      const eventEmitter = new EventEmitter();
      eventEmitter.on('data', (data) => {
      console.log(data); // 输出 '成功获取数据'
      });
      function fetchData() {
      setTimeout(() => {
      const data = '成功获取数据';
      eventEmitter.emit('data', data);
      }, 1000);
      }
      fetchData();
  2. 问题
    • 事件管理复杂:需要手动管理事件的注册和注销,容易出现内存泄漏。
    • 事件顺序难以控制:多个事件的处理顺序难以保证,容易导致逻辑混乱。

三、Promise

  1. 定义
    • Promise 是一个代表了异步操作最终完成或失败的对象。它提供了一种更简洁、更灵活的方式来处理异步操作。
    • 示例:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      function fetchData() {
      return new Promise((resolve, reject) => {
      setTimeout(() => {
      const data = '成功获取数据';
      resolve(data);
      }, 1000);
      });
      }
      fetchData().then((data) => {
      console.log(data); // 输出 '成功获取数据'
      }).catch((error) => {
      console.error(error);
      });
  2. 优势
    • 链式调用Promisethen 方法返回一个新的 Promise 对象,可以进行链式调用,使代码更简洁、更易读。
      1
      2
      3
      4
      5
      fetchData()
      .then(data1 => process(data1))
      .then(data2 => save(data2))
      .then(result => console.log(result))
      .catch(error => console.error(error));
    • 错误处理Promisecatch 方法可以捕获链中任何位置的错误,简化了错误处理逻辑。
      1
      2
      3
      4
      5
      fetchData()
      .then(data1 => process(data1))
      .then(data2 => save(data2))
      .then(result => console.log(result))
      .catch(error => console.error(error));
    • 状态管理Promise 有三种状态(Pending、Fulfilled、Rejected),状态一旦改变就不可逆,这使得异步操作的状态管理更加明确。
    • 静态方法Promise 提供了多种静态方法(如 Promise.allPromise.racePromise.resolvePromise.reject),可以方便地处理多个异步操作。
      1
      2
      3
      4
      5
      6
      7
      8
      const promise1 = Promise.resolve(1);
      const promise2 = Promise.resolve(2);
      const promise3 = Promise.resolve(3);
      Promise.all([promise1, promise2, promise3]).then((results) => {
      console.log(results); // 输出 [1, 2, 3]
      }).catch((error) => {
      console.error(error);
      });

四、总结

  • 回调函数
    • 优点:简单直接,适用于简单的异步操作。
    • 缺点:回调地狱,错误处理困难,代码难以维护。
  • 事件
    • 优点:适用于多对多的事件驱动模型,适用于DOM事件处理。
    • 缺点:事件管理复杂,事件顺序难以控制,容易导致逻辑混乱。
  • Promise
    • 优点:链式调用,错误处理简单,状态管理明确,提供多种静态方法,代码更简洁、更易读、更易维护。
    • 缺点:学习曲线稍陡,需要理解Promise的三种状态和链式调用的机制。
      通过使用 Promise,可以更有效地处理复杂的异步操作,使代码更加简洁、易读和易维护。