Promise 为我们处理异步操作提供了一种更优雅、更高效的方式,帮助我们摆脱回调地狱的困境,让代码逻辑更加清晰、易于维护。
一、Promise 的核心概念与设计初衷
设计背景
在 JavaScript 发展早期,异步编程主要依赖回调函数来实现。
- 回调函数,简单来说,就是作为参数传递给另一个函数,并在该函数完成某些操作后被调用的函数 。
- 比如在
setTimeout
中,我们传入的用于延迟执行的函数就是回调函数。在处理简单异步任务时,回调函数表现良好,但随着业务逻辑复杂度的提升,问题逐渐浮现。
当需要依次执行多个异步操作,且后一个操作依赖前一个操作的结果时,就会出现回调函数层层嵌套的情况,这便是所谓的 "回调地狱" 。
以常见的读取文件并处理数据为例,假设我们需要读取一个 JSON 文件,解析其中的数据,然后根据解析结果读取另一个相关文件,最后再对第二个文件的数据进行处理。使用传统回调函数实现的代码可能如下:
javascript
fs.readFile('firstFile.json', 'utf8', function (err, data1) {
if (err) {
console.error(err);
return;
}
const json1 = JSON.parse(data1);
fs.readFile(json1.relatedFile, 'utf8', function (err, data2) {
if (err) {
console.error(err);
return;
}
const json2 = JSON.parse(data2);
// 对第二个文件的数据进行处理
console.log('处理结果:', json2);
});
});
- 在这段代码中,
fs.readFile
是用于读取文件的异步函数,它接受文件名、编码格式以及一个回调函数作为参数。 - 当文件读取完成后,会调用这个回调函数,并将可能出现的错误
err
以及读取到的数据data
作为参数传入。 - 由于后一个文件的读取依赖前一个文件的解析结果,导致了回调函数的嵌套。
- 如果这种操作链继续增长,代码将呈现出金字塔形状,嵌套层次不断增加,变得极为复杂。
这种层层嵌套的代码结构,不仅使代码可读性大打折扣,维护和调试也变得异常困难。只要其中某一层的回调函数出现错误,排查问题的难度就会急剧上升。而且,随着嵌套层数增多,代码逻辑的理解成本呈指数级增长,很容易出现逻辑混乱。
Promise 正是为了解决这些问题而诞生的。
- Promise 是 JavaScript 中用于处理异步操作的对象,它代表了一个异步操作的最终完成(成功)或者失败。
- 简单来说,Promise 就像是一个容器,封装了一个异步操作及其结果。
Promise 通过链式调用的方式来组织异步操作。使用 Promise 改写上述代码如下:
javascript
const fs = require('fs').promises;
fs.readFile('firstFile.json', 'utf8')
.then(data1 => {
const json1 = JSON.parse(data1);
return fs.readFile(json1.relatedFile, 'utf8');
})
.then(data2 => {
const json2 = JSON.parse(data2);
console.log('处理结果:', json2);
})
.catch(err => {
console.error(err);
});
在这段代码中,fs.readFile
返回的是一个 Promise 对象,通过then
方法可以处理异步操作成功后的结果,catch
方法则用于捕获异步操作过程中发生的错误。
Promise 三大状态及特性
- pending(进行中) :Promise 的初始状态,此时异步操作正在执行,结果尚未可知,既未成功也未失败。
- 例如,发起一个网络请求后,在等待服务器响应期间,对应的 Promise 就处于 pending 状态。
- fulfilled(已成功) :当异步操作顺利完成,Promise 状态从 pending 转变为 fulfilled。一旦进入该状态,就无法再更改。
- 如网络请求成功获取到数据,对应的 Promise 会变为 fulfilled 状态。
- rejected(已失败) :若异步操作遭遇错误或异常,Promise 状态从 pending 转变为 rejected,该状态同样不可更改。
- 例如网络请求超时或服务器返回错误状态码,相关 Promise 将进入 rejected 状态。
一旦 Promise 的状态从 pending 转变为 fulfilled 或者 rejected,就不能再改变。
二、Promise 的使用
基础创建
通过 new Promise()
构造函数来创建 Promise
对象,该构造函数接收一个执行器函数作为参数。这个执行器函数有两个参数:resolve
和 reject
,它们是用于控制 Promise
状态转变的函数。
resolve
:当异步操作成功完成时,调用resolve
函数将Promise
的状态从pending
变为fulfilled
,并且可以传递一个值作为操作的结果。reject
:当异步操作失败时,调用reject
函数将Promise
的状态从pending
变为rejected
,通常会传递一个错误信息。例如:
javascript
// 模拟随机成功或失败的异步操作
const randomPromise = new Promise((resolve, reject) => {
if (Math.random() > 0.5) {
resolve('恭喜,操作成功!');
} else {
reject('很遗憾,操作失败');
}
});
在这段代码中,利用Math.random()
函数生成随机数来模拟异步操作的成功或失败,并通过resolve
和reject
改变 Promise 状态。
链式调用
Promise 的链式调用是其强大功能的体现。通过不断调用 then()
方法处理前一个 Promise 的结果,并返回新的 Promise,从而形成连贯的异步操作链条。
then
方法是链式调用的核心,它接收两个可选参数:
- 第一个参数是成功回调函数,当 Promise 成功(状态变为
fulfilled
)时会执行该函数,并将resolve
传递的值作为参数传入。 - 第二个参数是失败回调函数,当 Promise 失败(状态变为
rejected
)时会执行该函数,并将reject
传递的错误信息作为参数传入。 - 在实际使用中,第二个参数使用较少,通常会用
.catch
方法来专门处理失败情况。
javascript
function stepOne() {
return new Promise((resolve) => {
setTimeout(() => {
resolve('第一步执行完毕');
}, 1000);
});
}
function stepTwo(result) {
return new Promise((resolve) => {
setTimeout(() => {
const newResult = result + ',接着执行第二步';
resolve(newResult);
}, 1000);
});
}
stepOne()
.then(stepTwo)
.then((finalResult) => {
console.log(finalResult);
});
- 其中,
stepOne
函数返回一个Promise
对象。在这个Promise
内部,使用setTimeout
模拟一个异步操作,该操作会在 1 秒后完成。 - 当 1 秒后,
setTimeout
的回调函数被执行,调用resolve('第一步执行完毕')
,这意味着这个Promise
成功完成,并且将字符串'第一步执行完毕'
作为结果传递传递给stepTwo
。 stepTwo
处理结果并返回新值,最终在第三个then
中输出完整的执行结果。
错误处理与收尾
catch
方法处理失败情况
catch
方法专门用于处理 Promise 的失败情况。当 Promise 的状态变为 rejected
时,会执行 catch
方法中传入的回调函数,并将 reject
传递的错误信息作为参数传入。
在链式调用中,catch
方法可以捕获前面所有 Promise 操作中抛出的错误,为错误处理提供了集中且统一的方式。
javascript
const promise = new Promise((resolve, reject) => {
reject('操作失败');
});
promise.catch((error) => {
console.error(error);
});
链式调用中的值穿透现象及其在错误处理中的应用
在 Promise 的链式调用中,当使用 then
或 catch
方法时,如果传入 null
或者未传入对应的回调函数,Promise 的结果会直接越过当前的 then
或 catch
方法,传递给链式调用中的下一个合适的处理函数,这种现象就被称为值穿透。
then
方法的值穿透 :then
方法若传入null
或者不传入某个回调函数,当 Promise 状态改变时,就会跳过该位置,将结果传递给下一个then
或catch
方法中合适的回调函数。
js
// 创建一个 Promise,1 秒后成功并传递结果
const promise = new Promise((resolve) => {
setTimeout(() => {
resolve('初始结果');
}, 1000);
});
// 链式调用 then 方法,演示 then 方法的值穿透
promise
.then(null) // 跳过成功回调,值穿透
.then((result) => {
console.log('第一个 then 接收的结果:', result);
return result + ' 经过第一次处理';
})
.then(null, (error) => {
// 由于前面的 Promise 是成功状态,此失败回调会被跳过
console.log('错误处理:', error);
})
.then((newResult) => {
console.log('第二个 then 接收的结果:', newResult);
});

catch
方法的值穿透 :catch
方法相当于then(null, onRejected)
的语法糖,专门用于处理 Promise 失败的情况。若在链式调用中使用catch
方法,且后续还有then
方法,当 Promise 状态为fulfilled
时,会跳过catch
方法,直接将结果传递给后续then
方法的成功回调函数。
js
// 创建一个成功的 Promise,1 秒后传递结果
const successfulPromise = new Promise((resolve) => {
setTimeout(() => {
resolve('操作成功');
}, 1000);
});
// 链式调用,包含 catch 和 then 方法,演示 catch 方法的值穿透
successfulPromise
.catch((error) => {
console.log('错误处理:', error);
})
.then((result) => {
console.log('后续 then 接收的结果:', result);
});

finally
方法进行收尾操作
finally
方法无论 Promise 的最终状态是 fulfilled
还是 rejected
,都会执行其传入的回调函数。这在需要进行一些清理工作,如关闭网络连接、释放资源等场景中非常有用。
finally
方法不接收 Promise 的结果或错误信息,它只是单纯地执行回调函数,并且会返回一个新的 Promise,其状态和值与原 Promise 相同。
javascript
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('操作成功');
}, 1000);
});
promise
.then((result) => {
console.log(result);
})
.finally(() => {
console.log('无论结果如何,都会执行这里');
});
多级 .catch
的就近捕获和全局捕获
就近捕获
在每个可能出错的Promise
操作后紧跟.catch
,这样可以针对性地处理当前Promise
产生的错误。
- 精准处理 :能够精确地处理每个
Promise
操作中出现的错误,针对不同的Promise
错误类型可以编写不同的处理逻辑。- 例如,在一系列
Promise
操作中,有些Promise
可能因为网络问题失败,而有些可能因为数据格式错误失败,通过就近捕获可以为每种错误类型提供专门的处理方式。
- 例如,在一系列
- 代码可读性高 :阅读代码时,可以很容易地找到每个
Promise
操作对应的错误处理部分。
javascript
const promise1 = new Promise((resolve, reject) => {
// 模拟异步操作,1秒后失败
setTimeout(() => {
reject('Promise1 出错了');
}, 1000);
});
const promise2 = new Promise((resolve, reject) => {
// 模拟异步操作,2秒后成功
setTimeout(() => {
resolve('Promise2 成功了');
}, 2000);
});
promise1
.catch(error => {
// 就近捕获promise1的错误
console.log('捕获到promise1的错误:', error);
})
.then(result => console.log('promise1处理结果:', result))
.then(() => promise2)
.catch(error => {
// 就近捕获promise2的错误
console.log('捕获到promise2的错误:', error);
})
.then(result => console.log('promise2处理结果:', result));

在这个例子中,promise1
和promise2
都有各自就近的.catch
来处理自身的错误。
全局捕获
在Promise
链式调用的末尾统一添加.catch
,用于处理前面所有Promise
操作中未被捕获的错误。
- 统一处理 :可以对整个
Promise
链中出现的所有错误进行统一的处理,例如记录错误日志、向用户显示统一的错误提示等。这样可以避免在每个.catch
中重复编写一些通用的错误处理代码。 - 简化代码结构 :当
Promise
链比较长,且大部分错误都可以采用相同的处理方式时,在末尾使用全局捕获可以使代码更加简洁,减少代码冗余。
js
const promise1 = new Promise((resolve, reject) => {
// 模拟异步操作,1秒后失败
setTimeout(() => {
reject('Promise1 出错了');
}, 1000);
});
const promise2 = new Promise((resolve, reject) => {
// 模拟异步操作,2秒后成功
setTimeout(() => {
resolve('Promise2 成功了');
}, 2000);
});
promise1
.then(result => console.log('promise1处理结果:', result))
.then(() => promise2)
.then(result => console.log('promise2处理结果:', result))
.catch(error => {
// 全局捕获错误
console.log('捕获到全局错误:', error);
});
- 创建了两个
Promise
对象,promise1
1 秒后失败,promise2
2 秒后成功。 - 在链式调用中,因
promise1
为rejected
状态,其后的then
方法回调均不执行,错误持续向下传递。 - 最终链式调用末尾的
.catch
方法捕获该错误,打印出'捕获到全局错误: Promise1 出错了'
,这就是全局捕获的体现。

两者的结合使用
在实际应用中,对于一些需要特殊处理的错误,可以使用就近捕获进行针对性处理;而对于那些可以统一处理的错误,或者作为一种兜底的错误处理方式,可以在Promise
链的末尾使用全局捕获。
reject
和 throw
的区别
reject
和 throw
都可用于抛出错误,但 reject
专门用于 Promise 内部改变状态为 rejected,而 throw
可在普通同步代码或 Promise 回调中抛出异常。
Promise 实例方法与静态方法
Promise 有实例方法和静态方法之分。
- 实例方法是通过 Promise 实例来调用的,例如
then
、catch
和finally
。这些方法用于处理特定 Promise 实例的结果或错误。 - 静态方法是直接通过
Promise
构造函数调用,它们操作的是一组 Promise 实例,用于更复杂的异步流程控制。
三、Promise 静态方法
Promise.all(iterable)
- 参数要求 :它接收一个可迭代对象(常见如数组)作为参数,且该可迭代对象中的每个元素都需为
Promise
实例。 - 并行执行与返回值 :
Promise.all
会让这些Promise
实例并行 执行,然后返回一个新的Promise
实例。 - 全部成功的情况 :只有当传入的所有
Promise
实例都成功(状态变为fulfilled
)时,返回的新Promise
才会进入成功状态。- 此时,新
Promise
的结果是一个数组,数组元素按传入时的顺序排列,依次对应每个Promise
实例的成功结果。
- 此时,新
- 存在失败的情况 :若传入的
Promise
实例中有任何一个失败(状态变为rejected
),新Promise
会立即进入失败状态,并返回第一个失败Promise
实例的错误信息。
js
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]
});
const promise4 = Promise.resolve(4);
const promise5 = Promise.reject(new Error('Promise 5 失败'));
const promise6 = Promise.resolve(6);
Promise.all([promise4, promise5, promise6]).catch((error) => {
console.log(error.message); // 输出: Promise 5 失败
});
Promise.allSettled(iterable)
- 参数要求 :接收一个可迭代对象作为参数,且该可迭代对象中的每个元素都需为
Promise
实例。 - 并行执行与返回值 :
Promise.allSettled
会让这些Promise
实例并行 执行,然后返回一个新的Promise
实例。 - 完成情况 :无论传入的
Promise
实例是成功还是失败,返回的新Promise
不会立刻有结果。它会一直等待传入的所有Promise
实例都执行完毕,才会进入解决状态,给出最终结果。- 此时,新
Promise
的结果是一个数组,数组元素按传入时的顺序排列,每个元素是一个对象。 - 对象包含
status
属性,值为"fulfilled"
或"rejected"
,分别表示对应Promise
实例的成功或失败状态。- 若
status
为"fulfilled"
,对象还有value
属性,其值为对应Promise
实例的成功结果。 - 若
status
为"rejected"
,对象则有reason
属性,其值为对应Promise
实例的失败原因。
- 若
- 此时,新
js
// 定义一个函数,用于创建一个会成功的 Promise
function createSuccessPromise() {
return new Promise((resolve) => {
setTimeout(() => {
resolve('任务成功');
}, 1000);
});
}
// 定义一个函数,用于创建一个会失败的 Promise
function createFailedPromise() {
return new Promise((_, reject) => {
setTimeout(() => {
reject(new Error('任务失败'));
}, 1500);
});
}
// 创建多个 Promise 实例
const promises = [
createSuccessPromise(),
createFailedPromise()
];
// 使用 Promise.allSettled 处理这些 Promise
Promise.allSettled(promises).then((results) => {
results.forEach((result, index) => {
console.log(`第 ${index + 1} 个任务的结果:`);
if (result.status === 'fulfilled') {
console.log(` 状态: ${result.status}`);
console.log(` 结果: ${result.value}`);
} else {
console.log(` 状态: ${result.status}`);
console.log(` 原因: ${result.reason.message}`);
}
});
});

Promise.race(iterable)
- 参数要求 :接收一个可迭代对象作为参数,该可迭代对象中的每个元素都必须是
Promise
实例。 - 并行执行与返回值 :
Promise.race
会使这些Promise
实例并行 执行,随后返回一个新的Promise
实例。 - 完成情况 :一旦传入的
Promise
实例中有任何一个率先解决(成功/失败),返回的新Promise
就会立即进入相应的解决状态,其状态和结果与率先解决的那个Promise
完全一致。
javascript
const slowerPromise = new Promise((resolve) => setTimeout(() => resolve('较慢的 Promise 结果'), 1000));
const fasterPromise = new Promise((resolve) => setTimeout(() => resolve('较快的 Promise 结果'), 500));
Promise.race([slowerPromise, fasterPromise]).then((result) => {
console.log(result); // 输出: 较快的 Promise 结果
});
Promise.any(iterable)
- 参数要求 :接收一个可迭代对象作为参数,该可迭代对象中的每个元素都必须是
Promise
实例。 - 并行执行与返回值 :
Promise.any
会使这些Promise
实例并行执行,随后返回一个新的Promise
实例。 - 完成情况 :
- 一旦传入的
Promise
实例中有任何一个率先成功解决,返回的新Promise
就会立即进入成功解决状态,其结果为该成功Promise
的值。 - 若所有传入的
Promise
实例最终都失败,返回的新Promise
则会失败,并且会返回一个AggregateError
对象,此对象包含所有失败Promise
的错误信息。
- 一旦传入的
javascript
// 创建一个失败的 Promise 实例
const failedPromise1 = Promise.reject(new Error('第一个 Promise 失败'));
// 创建一个成功的 Promise 实例
const successfulPromise = Promise.resolve(12);
// 创建另一个失败的 Promise 实例
const failedPromise2 = Promise.reject(new Error('第二个 Promise 失败'));
// 使用 Promise.any 处理一组 Promise,只要有一个成功就返回成功 Promise 的值
Promise.any([failedPromise1, successfulPromise, failedPromise2]).then((result) => {
console.log(`成功的结果是: ${result}`);
});
// 创建两个失败的 Promise 实例
const allFailedPromise1 = Promise.reject(new Error('第三个 Promise 失败'));
const allFailedPromise2 = Promise.reject(new Error('第四个 Promise 失败'));
// 当所有 Promise 都失败时,Promise.any 返回的 Promise 会被拒绝,并抛出 AggregateError
Promise.any([allFailedPromise1, allFailedPromise2]).catch((error) => {
console.log(`错误信息: ${error}`);
});

- 对于第一组
Promise
,Promise.any
遇到promise12
成功解决,就返回其值12
。 - 对于第二组
Promise
,所有Promise
都失败,Promise.any
抛出AggregateError
类型的错误。- 这个错误对象包含了所有失败
Promise
的错误信息,而All promises were rejected
是其默认的错误提示文本 。
- 这个错误对象包含了所有失败
Promise.resolve(value)
- 参数要求 :接收一个值作为参数,这个值可以是普通数据类型、
Promise
实例或者thenable
对象(具有then
方法的对象)。 - 返回值 :返回一个
Promise
实例。 - 完成情况 :
- 若传入的值是普通数据类型,返回的
Promise
实例状态会立即变为成功,且结果为传入的值。 - 若传入的值本身就是一个
Promise
,则直接返回该Promise
。 - 若传入的是一个
thenable
对象,会将其转换为一个Promise
,并依据then
方法的执行结果来确定新Promise
的状态和结果。
- 若传入的值是普通数据类型,返回的
javascript
// 使用 Promise.resolve 处理普通数据类型
const plainValue = 16;
const promiseFromPlainValue = Promise.resolve(plainValue);
promiseFromPlainValue.then((resolvedResult) => {
console.log(`普通数据类型转换后的 Promise 结果: ${resolvedResult}`);
});
// 定义一个 thenable 对象
const thenableObject = {
then: function (resolveFunction) {
resolveFunction(17);
}
};
// 使用 Promise.resolve 将 thenable 对象转换为 Promise
const promiseFromThenable = Promise.resolve(thenableObject);
promiseFromThenable.then((resultFromThenable) => {
console.log(`thenable 对象转换后的 Promise 结果: ${resultFromThenable}`);
});

Promise.reject(reason)
- 参数要求 :接收一个值作为参数,这个值可以是任意类型,包括但不限于字符串、数字、对象、错误实例等,该值将作为
Promise
被拒绝的原因。 - 返回值 :返回一个状态为失败的
Promise
实例。 - 完成情况 :返回的
Promise
实例状态会立即变为失败(rejected
),其拒绝原因就是传入的参数。- 后续可以通过
catch
方法来捕获并处理这个拒绝原因,也可以在then
方法中传入第二个回调函数来处理拒绝情况。
- 后续可以通过
javascript
// 定义一个函数,模拟异步操作,根据条件决定返回成功或失败的 Promise
function performAsyncOperation(shouldSucceed) {
if (shouldSucceed) {
return Promise.resolve('操作成功');
} else {
return Promise.reject(new Error('操作由于某些原因失败'));
}
}
// 场景 1: 操作失败,使用 catch 方法处理
const operation1 = performAsyncOperation(false);
operation1.catch((error) => {
console.log('使用 catch 捕获错误:', error.message);
});
// 场景 2: 操作失败,使用 then 方法的第二个回调函数处理
const operation2 = performAsyncOperation(false);
operation2.then(
(result) => {
console.log('这不会执行,因为操作失败:', result);
},
(error) => {
console.log('使用 then 的第二个回调处理错误:', error.message);
}
);
// 场景 3: 操作成功,不会触发拒绝处理
const operation3 = performAsyncOperation(true);
operation3
.then((result) => {
console.log('操作成功:', result);
})
.catch((error) => {
console.log('这不会执行,因为操作成功:', error.message);
});

Promise 静态方法总结与对比
-
Promise.all
:适用于需要等待所有异步操作都成功完成后再进行下一步的场景。- 例如,在一个页面加载时需要同时请求多个资源(如图片、数据接口等),只有当所有资源都成功获取后,页面才进行渲染。
- 它与其他方法的区别在于,只要有一个 Promise 失败,整个操作就失败,并且返回第一个失败的错误信息。
-
Promise.allSettled
:适用于希望了解所有异步操作的最终结果,且无论结果成功与否的场景。- 比如在进行多项任务的批量处理时,需要统计每个任务的执行情况,而不希望一个任务的失败影响整体流程。
- 与
Promise.all
不同,它不会因为某个 Promise 失败而提前终止,而是等待所有 Promise 都有结果后返回包含每个 Promise 状态和结果的数组。
-
Promise.race
:适用于只关心多个异步操作中最先完成的结果的场景。- 例如在多个请求相同数据的接口中,哪个接口先返回数据就使用哪个,提高响应速度。
- 只要有一个 Promise 解决(成功或失败),它就返回该 Promise 的结果。
-
Promise.any
:当需要获取多个异步操作中第一个成功的结果时使用。- 比如在多个备用服务中,只要有一个可用并成功响应就可以。
- 与
Promise.race
的区别在于,Promise.any
只关注成功的 Promise,若所有 Promise 都失败才返回错误。
-
Promise.resolve
:主要用于将普通值、Promise 实例或 thenable 对象转换为 Promise 实例,方便在 Promise 链式调用中统一处理。- 例如在处理可能返回不同类型值的函数结果时,将其转换为 Promise 以继续链式操作。
-
Promise.reject
:创建一个状态为失败的 Promise 实例,用于在异步操作开始前就明确表示失败的情况。- 比如在函数参数校验不通过时,立即返回一个失败的 Promise。
四、Promise 应用场景
数据获取
在现代网页开发里,从服务器获取数据是极为常见的操作。开发一个新闻网站,需要从服务器获取新闻列表的 JSON 数据,或者在电商平台中,获取商品详情的相关数据。
Promise 能够将数据请求的异步操作封装起来。开发者可以定义一个函数,在函数内部发起 AJAX 请求,并返回一个 Promise 对象。
- 这个 Promise 对象在请求成功时会进入 resolved 状态,传递回服务器返回的数据;在请求失败时会进入 rejected 状态,传递错误信息。
- 这样,在调用该函数时,就可以使用
.then()
方法处理成功情况,用.catch()
方法处理失败情况。 - 在处理多个相关的数据请求时,还能通过 Promise 的链式调用或者静态方法,更好地控制请求的顺序和流程。
图片加载
在网页设计中,当页面存在多个图片需要加载时,情况会变得复杂。比如一个图片展示页面,要同时加载数十张图片,如果不进行有效的管理,可能会出现部分图片未加载完成就显示页面,导致用户体验不佳。
可以创建一个 Promise 数组,为每个图片的加载操作创建一个对应的 Promise 对象。
- 在这个 Promise 对象内部,监听图片的
load
和error
事件。 - 当图片成功加载时,将 Promise 状态设置为 resolved。
- 当图片加载失败时,将状态设置为 rejected。
接着,使用Promise.all
方法来监听这个 Promise 数组。
Promise.all
会等待数组中所有的 Promise 都变为 resolved 状态后,才会返回一个新的 Promise,该 Promise 的结果是一个包含所有图片加载结果的数组。- 这样,就可以在所有图片都加载完成后,执行相应的操作,比如显示加载完成的提示信息、调整图片布局等。
- 如果其中有任何一个图片加载失败,
Promise.all
返回的 Promise 就会立即进入 rejected 状态,并传递第一个失败的错误信息。
文件操作
在 Web 应用里,处理用户上传的本地文件是极为常见的需求,这既包括常规的文件读取操作,也涉及大文件上传的场景。
对于常规文件读取,以在线文档编辑工具为例,用户可能上传一个 JSON 文件,应用需要读取并解析文件内容;在图片处理应用中,用户上传图片文件,应用则要读取文件并显示预览。
- 可以创建一个函数,在函数内部使用浏览器提供的文件读取 API(如
FileReader
)来读取文件内容。由于FileReader
的操作是异步的,存在读取成功或失败的情况。- 借助 Promise 可以将这个过程封装起来,当文件读取成功时,把 Promise 状态设置为 resolved,并传递读取到的文件内容;当读取失败时,将状态设置为 rejected,并传递错误信息。
- 调用这些封装好的函数时,可以通过
.then()
方法对读取到的文件内容进行处理,比如解析 JSON 文件中的数据、显示图片预览等;使用.catch()
方法处理读取失败的情况,给用户相应的提示。
而在大文件上传方面,比如视频分享网站,用户可能需要上传时长较长、占用空间大的视频文件;在企业云盘服务中,用户也可能上传大容量的办公文档、设计素材等。
-
由于文件过大,一次性上传可能会导致网络拥堵、请求超时等问题,通常会采用切片上传的方式。
-
可以将大文件切割成多个小块,为每个小块的上传操作创建一个 Promise 对象。
- 在 Promise 内部,发起上传请求,监听上传的进度和结果。
- 当一个小块上传成功时,将对应的 Promise 状态设置为 resolved;
- 若上传失败,则设置为 rejected。
- 然后使用
Promise.all
等方法来管理这些 Promise,确保所有小块都上传成功。
-
在所有小块都上传成功后,可以在
.then()
方法中通知服务器进行文件的合并操作;若有小块上传失败,在.catch()
方法中提示用户上传出现问题,并可以提供重试机制。
动画效果
在实现复杂的动画效果时,动画的执行顺序和时机非常重要。例如,在一个游戏界面中,可能有多个角色的动画需要按顺序播放,或者在一个广告页面中,多个元素的动画要配合完成一个整体的效果。
Promise 可以用来管理这些动画的异步执行顺序。
- 对于每个动画操作,可以将其封装在一个 Promise 对象中。
- 在 Promise 内部,监听动画的开始和结束事件,当动画开始时,触发相应的动画效果;当动画结束时,将 Promise 状态设置为 resolved。
- 通过
Promise.race
方法,可以监听多个动画中哪个先完成。-
Promise.race
会返回一个新的 Promise,它的状态会在传入的多个 Promise 中有一个变为 resolved 或 rejected 时立即改变。 -
这样,在多个动画同时执行时,就可以根据最先完成的动画来执行相应的后续操作,比如触发下一个动画、显示提示信息等。
-
表单验证
当用户提交表单时,表单数据的验证是必不可少的步骤。例如,在注册页面中,需要验证用户名是否已被注册、密码是否符合强度要求;在登录页面中,需要验证用户名和密码是否正确。这些验证操作通常是异步的,因为可能需要与服务器进行交互。
使用 Promise 可以将表单验证的异步操作封装起来。
- 为每个验证规则创建一个函数,在函数内部发起与服务器的交互请求(如检查用户名是否已存在),并返回一个 Promise 对象。
- 在请求过程中,如果验证通过,将 Promise 状态设置为 resolved;如果验证不通过,将状态设置为 rejected,并传递相应的错误信息。
在表单提交时,可以调用这些验证函数,并通过 Promise 的链式调用或者Promise.all
方法来处理所有的验证操作。如果所有验证都通过,就可以继续进行表单数据的提交;如果有任何一个验证不通过,就根据返回的错误信息提示用户,告知具体的错误内容。