Promise
promise
JavaScript 中的 Promise 是一种用于异步编程的对象,它代表了一个异步操作的最终完成(或失败)及其结果值。Promise 有三种状态:
- Pending(等待中):初始状态,既不是成功,也不是失败状态。
- Fulfilled(已成功):意味着操作成功完成。
- Rejected(已失败):意味着操作失败。
一个Promise对象有以下几个方法:
Promise.prototype.then():在Promise成功时执行,可以链式调用。Promise.prototype.catch():在Promise失败时执行,用于错误处理。Promise.prototype.finally():无论Promise成功还是失败,都会执行。
创建 Promise
创建一个 Promise 对象通常使用 new Promise 构造函数,并提供一个执行器函数(executor function),它将在 Promise 创建后立即执行:
1 | let myPromise = new Promise((resolve, reject) => { |
使用 Promise
1 | myPromise.then((value) => { |
Promise 链
Promise 可以被链式调用,因为 then() 和 catch() 方法都会返回一个新的 Promise 对象:
1 | myPromise |
Promise.all()
Promise.all() 方法用于处理多个 Promise 对象,并且只有当所有 Promise 都成功时,才会返回结果:
1 | Promise.all([promise1, promise2, promise3]) |
Promise.race()
Promise.race() 方法同样用于处理多个 Promise 对象,但是只要有一个 Promise 完成(无论是成功还是失败),就会立即返回结果:
1 | Promise.race([promise1, promise2, promise3]) |
Promise 是 JavaScript 异步编程的基础,提供了一种更加清晰和可控的方式来处理异步操作。
使用场景
JavaScript 中的 Promise 被广泛用于处理异步操作,以下是一些常见的使用场景:
- 异步请求(如 AJAX):
Promise可以用于处理 HTTP 请求,例如使用fetchAPI 时,fetch本身返回一个Promise对象,可以链式调用then和catch来处理请求成功和失败的情况。1
2
3
4fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error)); - 定时操作(如 setTimeout):
虽然setTimeout本身不是基于Promise的,但你可以创建一个返回Promise的函数来模拟异步延迟。1
2
3
4function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
delay(1000).then(() => console.log('1 second passed')); - 文件读取和写入:
在 Node.js 中,fs模块提供了基于Promise的 API(例如fs.promises),允许你以异步方式读取和写入文件。1
2
3
4const fs = require('fs').promises;
fs.readFile('file.txt', 'utf8')
.then(data => console.log(data))
.catch(err => console.error(err)); - 数据库操作:
许多数据库库提供了基于Promise的 API,使得你可以以异步方式查询和操作数据库。1
2
3database.query('SELECT * FROM users')
.then(users => console.log(users))
.catch(error => console.error(error)); - 并行执行多个异步操作:
使用Promise.all可以同时执行多个异步操作,并在所有操作都成功完成时返回结果。1
2
3
4
5
6
7Promise.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)); - 串行执行多个异步操作:
通过链式调用then方法,可以确保异步操作按顺序执行。1
2
3
4
5
6
7
8
9
10fetch(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)); - 错误处理:
Promise的catch方法提供了一个集中处理异步操作中出现错误的地方。1
2
3
4
5
6fetch(url)
.then(response => response.json())
.catch(error => {
// 处理请求或解析过程中的错误
console.error('Failed to fetch:', error);
}); - 动态加载模块:
在支持动态import()的环境中,import()返回一个Promise,可以用来按需加载 JavaScript 模块。1
2
3
4
5import(/* webpackChunkName: "feature-module" */ './feature-module')
.then(module => {
module.loadFeature();
})
.catch(error => console.error('Module loading failed:', error)); - 事件监听:
虽然不是直接使用Promise,但可以通过封装事件监听器来返回Promise,使得可以以异步方式等待某个事件的发生。1
2
3
4
5
6function 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和awaitPromise 和 async/await 都是JavaScript中处理异步操作的工具,但它们在语法和使用上有所不同。以下是它们之间的一些主要区别:
- 语法和可读性:
- Promise:使用
.then()和.catch()方法来处理异步操作的结果和错误。代码可能看起来像“回调地狱”(callback hell),尤其是在多个异步操作嵌套时。 - async/await:提供了一种更接近同步代码的写法,使得异步代码更易于阅读和维护。
async关键字用于声明一个函数是异步的,而await关键字用于等待一个Promise解决。
- Promise:使用
- 错误处理:
- Promise:错误通过
.catch()方法捕获,这要求每个异步操作都必须被包裹在try/catch块中,或者链式调用.catch()。 - async/await:错误可以通过传统的
try/catch块捕获,这使得错误处理更直观和一致。
- Promise:错误通过
- 控制流:
- Promise:需要链式调用
.then()方法来处理后续的异步操作,这可能导致代码难以跟踪。 - async/await:允许你以线性方式编写异步代码,类似于同步代码,使得控制流更清晰。
- Promise:需要链式调用
- 性能:
- Promise:在某些情况下,
Promise可能比async/await稍微快一些,因为async/await实际上是基于Promise实现的。 - async/await:虽然它提供了更好的语法糖,但在底层,
async/await仍然依赖于Promise,因此性能差异通常可以忽略不计。
- Promise:在某些情况下,
- 使用场景:
- Promise:适用于需要链式调用多个异步操作的场景,或者当你需要在异步操作完成后执行多个回调函数时。
- async/await:适用于需要等待单个异步操作完成的场景,或者当你想要以同步的方式编写异步代码时。
- 兼容性:
- Promise:在ES6中引入,已被广泛支持。
- async/await:在ES2017中引入,兼容性较好,但可能在一些旧的JavaScript环境中不被支持。
- 代码简洁性:
- Promise:在处理多个异步操作时,代码可能会变得复杂和冗长。
- async/await:通常可以减少代码量,使其更加简洁和易于理解。
总的来说,async/await是建立在Promise之上的语法糖,它提供了一种更简洁、更直观的方式来处理异步操作。然而,理解Promise的工作原理对于深入理解async/await以及处理更复杂的异步逻辑是非常重要的。
fetch
fetch 是 JavaScript 中用于发起网络请求的函数,它返回一个 Promise 对象,该对象在请求完成时解析为一个 Response 对象。fetch 提供了一种简单、现代的方式来处理 HTTP 请求和响应。下面是一些关于 fetch 的基本用法和示例:
基本用法
发起 GET 请求
1 | fetch('https://api.example.com/data') |
在这个示例中,fetch 发起一个 GET 请求到指定的 URL。然后使用 .then() 方法处理响应。首先检查响应是否成功(response.ok),如果成功,则将响应解析为 JSON 格式。最后,处理解析后的数据或捕获并处理错误。
发起 POST 请求
1 | fetch('https://api.example.com/data', { |
在这个 POST 请求的示例中,我们设置了请求方法为 POST,并指定了请求头和请求体。请求体被设置为 JSON 字符串,因此需要在请求头中指定 Content-Type 为 application/json。
使用 async/await
使用 async 和 await 可以使 fetch 请求的代码更加简洁和易于理解:
1 | async function fetchData() { |
响应类型
.json():将响应体解析为 JSON 格式。.text():将响应体解析为文本。.blob():将响应体解析为 Blob 对象,适用于二进制数据。.formData():将响应体解析为 FormData 对象,适用于表单数据。
错误处理
- 网络错误:如果网络请求失败(例如,无法连接到服务器),
fetch会抛出一个异常,可以在.catch()中捕获。 - HTTP 错误:如果服务器返回一个非 200 系列的状态码,
fetch仍然会解析为Promise,但response.ok会为false。需要手动检查response.ok或response.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 | async function fetchData() { |
在这个例子中,fetchData 是一个 async 函数,它使用 await 等待 fetch 请求的结果。如果请求成功,它将解析 JSON 并打印数据。如果请求失败,它将捕获错误并打印错误信息。
错误处理
async/await 允许使用传统的 try/catch 块来处理错误,这使得错误处理更加直观。
1 | async function fetchData() { |
并行执行异步操作
虽然 async/await 使得代码看起来是同步的,但它仍然可以并行执行异步操作。
1 | async function fetchMultipleUrls(urls) { |
在这个例子中,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)
- 定义
- 回调函数是一种将函数作为参数传递给另一个函数,并在某个操作完成后调用该函数的方式。这是早期 JavaScript 中处理异步操作的主要方式。
- 示例:
1
2
3
4
5
6
7
8
9function fetchData(callback) {
setTimeout(() => {
const data = '成功获取数据';
callback(data);
}, 1000);
}
fetchData((data) => {
console.log(data); // 输出 '成功获取数据'
});
- 问题
- 回调地狱(Callback Hell):当多个异步操作需要顺序执行时,回调函数会嵌套多层,导致代码难以阅读和维护。
1
2
3
4
5
6
7fetchData((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
19fetchData((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);
});
});
});
- 回调地狱(Callback Hell):当多个异步操作需要顺序执行时,回调函数会嵌套多层,导致代码难以阅读和维护。
二、事件(Event)
- 定义
- 事件是一种基于事件驱动模型的异步处理方式。通过监听事件,当事件发生时执行特定的处理函数。
- 示例:
1
2
3
4
5
6
7
8
9
10
11const eventEmitter = new EventEmitter();
eventEmitter.on('data', (data) => {
console.log(data); // 输出 '成功获取数据'
});
function fetchData() {
setTimeout(() => {
const data = '成功获取数据';
eventEmitter.emit('data', data);
}, 1000);
}
fetchData();
- 问题
- 事件管理复杂:需要手动管理事件的注册和注销,容易出现内存泄漏。
- 事件顺序难以控制:多个事件的处理顺序难以保证,容易导致逻辑混乱。
三、Promise
- 定义
Promise是一个代表了异步操作最终完成或失败的对象。它提供了一种更简洁、更灵活的方式来处理异步操作。- 示例:
1
2
3
4
5
6
7
8
9
10
11
12
13function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
const data = '成功获取数据';
resolve(data);
}, 1000);
});
}
fetchData().then((data) => {
console.log(data); // 输出 '成功获取数据'
}).catch((error) => {
console.error(error);
});
- 优势
- 链式调用:
Promise的then方法返回一个新的Promise对象,可以进行链式调用,使代码更简洁、更易读。1
2
3
4
5fetchData()
.then(data1 => process(data1))
.then(data2 => save(data2))
.then(result => console.log(result))
.catch(error => console.error(error)); - 错误处理:
Promise的catch方法可以捕获链中任何位置的错误,简化了错误处理逻辑。1
2
3
4
5fetchData()
.then(data1 => process(data1))
.then(data2 => save(data2))
.then(result => console.log(result))
.catch(error => console.error(error)); - 状态管理:
Promise有三种状态(Pending、Fulfilled、Rejected),状态一旦改变就不可逆,这使得异步操作的状态管理更加明确。 - 静态方法:
Promise提供了多种静态方法(如Promise.all、Promise.race、Promise.resolve、Promise.reject),可以方便地处理多个异步操作。1
2
3
4
5
6
7
8const 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,可以更有效地处理复杂的异步操作,使代码更加简洁、易读和易维护。