JavaScript异步编程: Promise状态机制、链式调用及静态方法解析

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 对象,该构造函数接收一个执行器函数作为参数。这个执行器函数有两个参数:resolvereject,它们是用于控制 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()函数生成随机数来模拟异步操作的成功或失败,并通过resolvereject改变 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 的链式调用中,当使用 thencatch 方法时,如果传入 null 或者未传入对应的回调函数,Promise 的结果会直接越过当前的 thencatch 方法,传递给链式调用中的下一个合适的处理函数,这种现象就被称为值穿透。

  • then 方法的值穿透then 方法若传入 null 或者不传入某个回调函数,当 Promise 状态改变时,就会跳过该位置,将结果传递给下一个 thencatch 方法中合适的回调函数。
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));

在这个例子中,promise1promise2都有各自就近的.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 秒后成功。
  • 在链式调用中,因 promise1rejected 状态,其后的 then 方法回调均不执行,错误持续向下传递。
  • 最终链式调用末尾的 .catch 方法捕获该错误,打印出 '捕获到全局错误: Promise1 出错了',这就是全局捕获的体现。
两者的结合使用

在实际应用中,对于一些需要特殊处理的错误,可以使用就近捕获进行针对性处理;而对于那些可以统一处理的错误,或者作为一种兜底的错误处理方式,可以在Promise链的末尾使用全局捕获。

rejectthrow 的区别

rejectthrow 都可用于抛出错误,但 reject 专门用于 Promise 内部改变状态为 rejected,而 throw 可在普通同步代码或 Promise 回调中抛出异常。

Promise 实例方法与静态方法

Promise 有实例方法和静态方法之分。

  • 实例方法是通过 Promise 实例来调用的,例如 thencatchfinally。这些方法用于处理特定 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}`); 
});
  • 对于第一组 PromisePromise.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 对象内部,监听图片的loaderror事件。
  • 当图片成功加载时,将 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方法来处理所有的验证操作。如果所有验证都通过,就可以继续进行表单数据的提交;如果有任何一个验证不通过,就根据返回的错误信息提示用户,告知具体的错误内容。

相关推荐
齐尹秦几秒前
CSS 轮廓(Outline)属性学习笔记
前端
齐尹秦3 分钟前
CSS 字体学习笔记
前端
入门级前端开发4 分钟前
css实现一键换肤
前端·css
xixixin_15 分钟前
css一些注意事项
前端·css
坊钰38 分钟前
【MySQL 数据库】增删查改操作CRUD(下)
java·前端·数据库·学习·mysql·html
excel41 分钟前
webpack 模块 第 六 节
前端
Watermelo61743 分钟前
Vue3+Vite前端项目部署后部分图片资源无法获取、动态路径图片资源报404错误的原因及解决方案
前端·vue.js·数据挖掘·前端框架·vue·运维开发·持续部署
好_快43 分钟前
Lodash源码阅读-flattenDepth
前端·javascript·源码阅读
好_快43 分钟前
Lodash源码阅读-baseWhile
前端·javascript·源码阅读
呆头呆脑~44 分钟前
阿里滑块 231 231纯算 水果滑块 拼图 1688滑块 某宝 大麦滑块 阿里231 验证码
javascript·爬虫·python·网络爬虫·wasm