Javascript Promise
Promise介绍
介绍
Promise 是异步编程的一种新的解决方案,它比之前的纯回调函数形式更加合理和强大。它用于处理异步操作,允许你在未来某个时间点获取操作的结果。
Promise 的表达
- 从语法上说:Promise 是一个构造函数。
- 从功能上说:Promise 实例对象可以用来封装一个异步操作,并在未来某时刻获取到其结果。
由于异步操作的结果具有不确定性,并不一定每次都能成功获取结果。Promise 技术通过状态标识不同的返回结果。
Promise 对象的三种状态:
pending:初始状态,表示尚未完成(等待中)。resolved/fulfilled:成功状态,表示异步操作已成功并获取到了结果。rejected:失败状态,表示异步操作失败,未能获取到结果。
Promise 的状态只能从
pending变为resolved或rejected,一旦状态发生变化就不能再改变。
状态的变化:
- 从
pending变为resolved,通过调用resolve函数实现。 - 从
pending变为rejected,通过调用reject函数实现。
Promise 只能改变一次状态,状态一旦确定下来,任何时间都可以获取到这个结果(除非发生错误导致结果获取失败)。
Promise 构造函数
使用 new Promise() 创建一个 Promise 实例对象。Promise 构造函数接受一个执行器函数(executor)作为参数,该函数包含两个参数:resolve 和 reject,这两个参数都是函数,用来通知 Promise 最终的执行结果。
1 | const promise = new Promise((resolve, reject) => { |
上面的代码输出顺序是:
1 | 1 |
执行流程解析:
- 构造
Promise时,执行器函数立即同步执行。 - 异步任务(如
setTimeout)延迟执行,最终通过调用resolve或reject来改变Promise的状态。 - 状态一旦变为
resolved或rejected,结果会被传递到then或catch中的相应回调函数。
Promise 的 then 方法
当 Promise 状态从 pending 变为 resolved 或 rejected 后,可以通过 then 方法分别指定成功和失败的回调函数。
value
- 当
Promise状态变为fulfilled(已成功)时,then()方法的第一个回调函数会接收一个参数,这个参数就是value。 - 这个
value通常是由resolve()函数传递的值。 - 也可以是链式调用中then的上级传递下来的值(
Promise状态变为fulfilled(已成功))
reason
- 当
Promise状态变为rejected(已失败)时,then()方法的第二个回调函数会接收一个参数,这个参数就是reason。 - 这个
reason通常是由reject()函数传递的值,表示Promise失败的原因。 - 也可以是链式调用中then的上级传递下来的值(
Promise状态变为rejected(已失败))
1 | promise.then( |
- 第一个回调函数:当 Promise 状态为
resolved时执行,参数value是上级then方法或Promise对象异步操作成功时传递的结果。 - 第二个回调函数:当 Promise 状态为
rejected时执行,参数reason是上级then方法或Promise对象异步操作失败时传递的原因。
例子:
1 | const promise = new Promise((resolve, reject) => { |
Promise 状态变化与捕获
Promise 的状态是不可逆的,一旦 resolve 或 reject 被调用,状态便会锁定为 resolved 或 rejected,无法再次更改。
Promise 可以通过 resolve、reject 或 throw 来改变状态。状态变化时会触发相应的回调函数,但如果发生异常,状态会自动变为 rejected,并且抛出的错误会被 .catch() 捕获。
1. 状态变化:resolve 和 reject
- 调用
resolve()会将 Promise 的状态从pending变为resolved,并传递成功的值。 - 调用
reject()会将状态从pending变为rejected,并传递拒绝的原因。
2. 主动抛出异常 throw
当 Promise 内部主动抛出异常时(例如使用 throw new Error()),Promise 的状态会自动变为 rejected,并且错误信息会传递给 .catch() 进行处理。
1 | const p = new Promise((resolve, reject) => { |
捕获失败状态
在实际开发中,推荐使用 .catch() 方法来处理 Promise 的失败情况,它相当于 .then() 的第二个参数,但更具可读性。
1 | promise |
获取Promise结果的时机
Promise 是用于处理异步操作的对象,无论异步任务何时完成,Promise 的结果都可以通过 .then() 方法进行获取。在 Promise 对象创建后,内部的异步任务会立即开始执行,但结果只会在任务完成后才能被访问。在这一过程中,.then() 方法提供了获取成功或失败结果的方式。
1. 异步任务开始执行,Promise 进入 pending 状态
一旦创建 Promise 对象,内部的异步任务(例如 setTimeout)会立即开始执行,但结果尚未传递到 resolve 或 reject 中,此时 Promise 处于 pending 状态。
示例代码:
1 | const promise = new Promise((resolve, reject) => { |
此时,异步任务开始执行,但 resolve 或 reject 还未被调用,promise 仍处于 pending 状态。
2. 异步任务执行完毕,Promise 状态变更为 resolved 或 rejected
异步任务完成后,Promise 状态会从 pending 变为 resolved(成功)或 rejected(失败)。无论状态如何变化,.then() 方法都会响应并获取相应的结果。
1 | promise.then( |
在此例中,.then() 在异步任务完成时,获取到 resolve 或 reject 传递的值。
3. .then() 可在异步任务完成前调用
Promise 的 .then() 方法可以在异步任务执行完成之前调用,不影响结果的获取。即使在异步任务还未完成时注册 .then(),当 Promise 的状态变为 resolved 或 rejected 时,注册的回调函数会自动执行。
示例代码:
1 | setTimeout(() => { |
即使 Promise 内部的任务已经执行完毕,调用 .then() 依然可以获得之前的结果。
4. 总结
Promise对象创建后,异步任务立即开始执行。.then()方法可以在异步任务开始执行前调用,也可以在异步任务完成后调用,结果都会被正确获取。- 当异步任务完成时,
Promise的状态从pending变为resolved或rejected,并触发相应的回调函数。 - 无论何时调用
.then(),只要Promise的状态已经变更,都会立即返回结果。
Promise API
1. Promise 构造函数:new Promise(excutor)
-
参数 excutor:接收两个回调函数
resolve和reject。 -
执行时机:executor 会在 Promise 内部立即同步调用,但异步操作会在执行器中执行。
-
resolve方法:请求成功时调用的回调函数,返回结果:1
value => {}
-
reject方法:请求被拒绝时调用的回调函数,返回结果:1
reason => {}
2. Promise.prototype.then 方法 (onResolved, onRejected) => {}
-
onResolved回调:当成功时调用,取到上面成功执行的结果:1
(value) => { console.log(value) }
-
onRejected回调:当被拒绝时调用,取到上面失败的结果:1
(reason) => { console.log(reason) }
-
说明:指定用于得到
value的成功回调,和用于得到reason的失败回调,并返回一个新的Promise对象,方便链式调用。
3. Promise.prototype.catch 方法 (onRejected) => {}
-
onRejected回调:被拒绝时的回调:1
(reason) => { console.log(reason) }
-
说明:
then()的语法糖,等同于.then(undefined, onRejected)。
4. Promise.resolve(value) 方法
- 功能:通过参数把成功的数据放到 Promise 对象上。
- 返回:一个成功的
Promise对象。
5. Promise.reject(reason) 方法
- 功能:通过参数把被拒绝的原因放到 Promise 对象上。
- 返回:一个被拒绝的
Promise对象。
6. Promise.all(promiseArr) 方法
- 参数:包含
Promise对象的数组promiseArr。 - 功能:返回一个新的
Promise,当数组中的所有Promise都成功时,返回成功值;如果有一个被拒绝,则返回该拒绝的reason。 - 返回:一个新的
Promise,返回结果是一个数组,数组顺序与promiseArr的顺序一致。
7. Promise.race(promiseArr) 方法
- 参数:包含
Promise对象的数组promiseArr。 - 功能:返回一个新的
Promise,只要其中任何一个Promise完成,无论成功或失败,都会立即返回这个Promise的结果。 - 返回:一个新的
Promise,第一个完成的Promise结果就是最终的结果。
示例代码:
1 | // 生成一个成功的返回值为1的promise对象 |
补充说明:
Promise.resolve()和Promise.reject()提供快速生成已成功或已失败状态的Promise,可以用于需要快速模拟或操作Promise状态的场景。(语法糖)Promise.all()适用于需要等待多个异步任务全部完成的场景,若有任一Promise失败,Promise.all()会立即返回失败。Promise.race()则用于竞速场景,返回最先完成的Promise结果,无论成功或失败。
Promise窜连多个任务
then返回的promise的状态
- promise.then返回的新的promise的状态由什么决定?
一句话回答:由then()指定的回调函数中自动执行的结果决定(then的上级任务)
.then() 返回值的三种情况:
- 如果回调函数中
return一个非Promise的值:Promise的状态为resolved,返回值为return的这个值。- 相当于return new Promise.resolve(“返回的非Promise值”)
1 | new Promise((resolve) => resolve('success')) |
- 如果回调函数中
return一个Promise对象:Promise的状态由返回的Promise决定,即外层Promise的状态和返回的Promise的状态保持一致。- 如果返回的
Promise状态是resolved,则外层Promise也是resolved,并返回这个Promise的成功值。 - 如果返回的
Promise状态是rejected,则外层Promise也是rejected,并返回这个Promise的失败原因。
- 如果返回的
1 | new Promise((resolve) => resolve('success')) |
- 如果回调函数中抛出异常:
Promise的状态为rejected,返回值为抛出的异常。
1 | new Promise((resolve) => resolve('success')) |
示例代码:
1 | // 创建一个Promise,初始resolve一个成功的值 |
用promise串联多个操作任务(链式调用)
- 调用多个
then()方法来处理异步任务即为串联多个操作任务,也为链式调用 - 链式调用的多个then方法里面可以包括不同的任务,可以有同步任务和异步任务
1 | // 使用 Promise 执行多个异步任务 |
Promise的异常穿透和中断链
异常穿透
- 当Promise链中发生错误时,它会沿着链向下传播,也就是说,如果有一个上级的then发生了错误,多个下级的then没有相应的错误处理方法,那么这个错误就会一直传播下去,直到遇到了相应的错误处理方法,这就是异常穿透
1 | .then( |
- 每个
.then()可以有两个回调函数:onFulfilled和onRejected。 - 如果没有提供
onRejected,它默认为reason => { throw reason; }。 - 如果在
.then()的onRejected回调中处理了错误并返回了一个非拒绝的值,链式调用会正常继续。 - 后续的
.then()调用将接收到前一个.then()或.catch()返回的值。 - 如果在
.then()调用中没有进行错误处理,错误会传播到最终的.catch()。
中断链
- 如果不希望下面方法的回调函数执行就可以返回一个pending状态的promise, 这样以下的任何then或者catch方法的回调函数都不会调用了,相当于对下级的then进行了中断
- 中断链式调用:
return new Promise(() => {}) - 这会返回一个永远处于pending状态的Promise,这effectively会阻止后续的
.then()或.catch()回调函数的执行。
1 | ```javascript |
总结
- 错误会沿着Promise链向下传播,直到被捕获。
- 异常穿透和中断链息息相关,通常一起使用
.catch()实际上是.then(null, errorHandler)的语法糖。- 在
.catch()块中返回一个永远处于pending状态的Promise(new Promise(() => {}))将阻止链式调用的进一步执行。
async函数
介绍
async函数是JavaScript中处理异步操作的一种方式,它建立在Promise之上,提供了更简洁的语法来处理异步代码。(语法糖)
特性
- async关键字用于声明异步函数
- async函数内部可以使用await关键字
- async函数总是返回一个Promise对象
基本使用示例
1 | //返回非Promise值 |
异步操作
1 | async function fn5() { |
注意事项
- async函数可以看作是使用Promise的语法糖,它简化了Promise的使用,使得异步代码更易于编写和理解。
- 在async函数中,可以使用await关键字等待一个Promise解决,这使得异步代码可以以同步的方式编写。
- 虽然async函数总是返回一个Promise,但这个Promise的状态和值取决于函数的执行结果。
- 处理async函数的错误可以使用try-catch语句,或者在调用时使用.catch()方法。
await函数及其处理异常
基本概念
await是一个表达式,用于等待异步操作的结果。它只能在async函数中使用。
使用
- await后面跟一个表达式,表示等待该表达式的结果
- await命令只能在async函数中使用(async函数中不一定非要使用await命令)
示例代码
1 | function fn() { |
- 完整的输出顺序是:
'abc'(立即输出)- (等待 2 秒)
1'hello'
- 当遇到await命令时,如果后面是Promise对象,JavaScript引擎会暂停执行,等待Promise对象resolve,然后将resolve的值作为await表达式的运算结果。
- 如果await后面不是Promise对象,会直接返回对应的值。
处理异常
- await命令后面的Promise对象,运行结果可能是rejected,所以最好把await命令放在try…catch代码块中。
1 | async function fn2() { |
Promise.all同时触发
- 多个await命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。
1 | // 非优化版本 |
实际应用
1 | async function fetchUserData(userId) { |
这个例子展示了如何在实际应用中使用async/await,包括错误处理和链式调用。
总结
await关键字提供了一种更直观的方式来处理异步操作,使得异步代码看起来更像同步代码,提高了代码的可读性和可维护性。然而,使用await时需要注意错误处理和性能优化,以确保代码的健壮性和效率。
扩展:网络请求
在 JavaScript 中,fetch() 是一个用于发起网络请求并处理响应的 API,基于 Promise,允许你异步地获取资源,例如通过 HTTP 请求获取数据。
fetch() 被引入以取代较早的 XMLHttpRequest,使得代码更简洁且易于处理异步操作。它支持常见的 HTTP 方法如 GET、POST、PUT、DELETE 等。
基本语法
1 | fetch(url, [options]) |
url:请求的 URL,字符串格式。options(可选):一个配置对象,用于设置请求的详细信息,例如请求方法、请求头、请求体等。
fetch() 的特点:
- 基于 Promise:
fetch()返回一个Promise,该Promise会在请求完成后解析为Response对象。 - 不会自动拒绝:即使服务器返回 404 或 500 错误,
fetch()也不会拒绝(不会进入.catch()),除非请求失败(如网络错误)。 - 默认是 GET 请求:如果不提供
options,默认会发起一个GET请求。
示例:发起一个 GET 请求
1 | fetch('https://api.example.com/data') |
解释:
- 首先调用
fetch()发送网络请求,返回一个Promise,解析为Response对象。 response.ok用于检查请求是否成功(即 HTTP 状态码为 200-299),如果不是,则抛出错误。response.json()将响应内容解析为 JSON 格式,返回一个Promise。- 在
.then()中处理解析后的数据。 .catch()用于捕获网络错误或代码中抛出的异常。
示例:发起一个 POST 请求
1 | fetch('https://api.example.com/data', { |
解释:
- 使用
method: 'POST'指定为 POST 请求。 - 使用
headers设置Content-Type为application/json,告诉服务器我们发送的是 JSON 数据。 - 使用
body发送请求体数据,需将对象转换为 JSON 字符串。
fetch() 的一些常见使用选项:
method:指定 HTTP 方法,如GET、POST、PUT、DELETE。headers:设置请求头,如Content-Type、Authorization。body:设置请求体内容(主要用于POST、PUT请求)。mode:请求的模式,如cors(默认,允许跨域请求)、no-cors(不允许跨域请求)、same-origin(仅限同源请求)。credentials:是否在跨域请求中发送 cookie,例如same-origin或include。cache:控制缓存模式,如default、no-store、reload、no-cache。
处理不同格式的响应
fetch() 的 Response 对象提供了多种解析方法,来处理不同类型的响应:
.json():解析响应为 JSON 格式,返回一个Promise。.text():解析响应为纯文本,返回一个Promise。.blob():解析响应为二进制数据(Blob),返回一个Promise。.arrayBuffer():解析响应为ArrayBuffer,用于处理低级二进制数据。
总结:
fetch()是现代 JavaScript 中处理网络请求的主要方式,它使得发送 HTTP 请求更加简单和直观。- 通过
Promise链,可以轻松处理异步网络请求的结果或捕获错误。 - 它比传统的
XMLHttpRequest更易读、简洁,并且有更广泛的支持选项,如mode、credentials等。
