深入解析Promise:从基础原理到async/await实战

Promise 是 ES6 引入的异步编程解决方案,本质是一个表示异步操作最终完成或失败的对象。它有三种状态:​

  • pending(进行中):初始状态,尚未完成或失败
  • fulfilled(已完成):异步操作成功完成
  • rejected(已拒绝):异步操作失败

一、Promise 构造函数

  • 语法 :new Promise((resolve, reject) => { /* 异步操作 */ })

  • 参数

    • resolve:是一个函数,当异步操作成功完成时调用,它接收一个参数,该参数会成为 Promise 解决(fulfilled)状态下.then()回调函数的参数。

    • reject:也是一个函数,当异步操作失败时调用,它接收一个参数,通常是一个错误对象,该对象会成为 Promise 拒绝(rejected)状态下.catch()回调函数的参数。

      // 修改为函数形式,每次调用返回一个新的 Promise
      const getRandomNumber = () => {
      return new Promise((resolve, reject) => {
      const random = Math.random();
      if (random > 0.5) {
      resolve(random);
      } else {
      reject(new Error('随机数小于0.5'));
      }
      });
      };

      // 第一次调用
      getRandomNumber()
      .then(result => console.log('第一次结果:', result))
      .catch(error => console.log('第一次失败:', error));

      // 第二次调用
      getRandomNumber()
      .then(result => console.log('第二次结果:', result))
      .catch(error => console.log('第二次失败:', error));

  • 函数形式 :将 getRandomNumber 定义为函数,每次调用 getRandomNumber() 会创建一个新的 Promise,确保两次调用独立执行。

  • 处理成功和失败 :每个调用都通过 .then() 处理成功结果,通过 .catch() 处理错误,确保无论随机数是否大于 0.5,都能看到输出。

二、Promise 中 resolve 和 reject 的源码解析与工作原理

在 JavaScript 引擎中,Promise构造函数里的resolvereject是其内部实现的核心部分,它们主要用于控制Promise对象状态的转变。虽然 JavaScript 引擎的底层实现是用 C++ 等语言编写,无法直接查看源码,但可以通过模拟实现来理解其工作原理和参数传入后的执行逻辑。

Promise对象通常包含状态属性、用于存储成功结果或失败原因的属性,以及存放then方法中回调函数的队列。以下是一个简化的Promise模拟实现:

复制代码
function MyPromise(executor) {
    // 初始状态为pending
    this.status = 'pending'; 
    // 成功的值,初始为null
    this.value = null; 
    // 失败的原因,初始为null
    this.reason = null; 
    // 成功回调函数队列
    this.onFulfilledCallbacks = []; 
    // 失败回调函数队列
    this.onRejectedCallbacks = []; 

    // 定义resolve函数
    const resolve = (val) => {
        if (this.status === 'pending') {
            this.status = 'fulfilled';
            this.value = val;
            // 依次执行成功回调函数队列中的函数
            this.onFulfilledCallbacks.forEach(callback => callback(this.value)); 
        }
    };

    // 定义reject函数
    const reject = (err) => {
        if (this.status === 'pending') {
            this.status ='rejected';
            this.reason = err;
            // 依次执行失败回调函数队列中的函数
            this.onRejectedCallbacks.forEach(callback => callback(this.reason)); 
        }
    };

    try {
        // 执行传入的executor函数,并传入resolve和reject
        executor(resolve, reject); 
    } catch (error) {
        // 如果executor执行过程中抛出错误,调用reject
        reject(error); 
    }
}

// 模拟then方法
MyPromise.prototype.then = function (onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function'? onFulfilled : val => val;
    onRejected = typeof onRejected === 'function'? onRejected : err => { throw err };

    if (this.status === 'fulfilled') {
        return new MyPromise((resolve, reject) => {
            try {
                const x = onFulfilled(this.value);
                resolvePromise(this, x, resolve, reject);
            } catch (error) {
                reject(error);
            }
        });
    }

    if (this.status ==='rejected') {
        return new MyPromise((resolve, reject) => {
            try {
                const x = onRejected(this.reason);
                resolvePromise(this, x, resolve, reject);
            } catch (error) {
                reject(error);
            }
        });
    }

    if (this.status === 'pending') {
        return new MyPromise((resolve, reject) => {
            this.onFulfilledCallbacks.push(() => {
                try {
                    const x = onFulfilled(this.value);
                    resolvePromise(this, x, resolve, reject);
                } catch (error) {
                    reject(error);
                }
            });

            this.onRejectedCallbacks.push(() => {
                try {
                    const x = onRejected(this.reason);
                    resolvePromise(this, x, resolve, reject);
                } catch (error) {
                    reject(error);
                }
            });
        });
    }
};

function resolvePromise(promise2, x, resolve, reject) {
    if (promise2 === x) {
        return reject(new TypeError('Chaining cycle detected for promise'));
    }

    if (x instanceof MyPromise) {
        x.then(resolve, reject);
    } else if (x!== null && (typeof x === 'object' || typeof x === 'function')) {
        let called = false;
        try {
            const then = x.then;
            if (typeof then === 'function') {
                then.call(x, y => {
                    if (called) return;
                    called = true;
                    resolvePromise(promise2, y, resolve, reject);
                }, err => {
                    if (called) return;
                    called = true;
                    reject(err);
                });
            } else {
                resolve(x);
            }
        } catch (error) {
            if (called) return;
            called = true;
            reject(error);
        }
    } else {
        resolve(x);
    }
}

1.resolve 函数的工作原理

resolve函数的主要作用是将Promisepending状态转换为fulfilled状态,并处理传入的参数。当resolve被调用时,参数传入后具体执行过程如下:

  • 检查状态 :首先判断当前Promise的状态是否为pending,只有在pending状态下才进行状态转换。
  • 更新状态和值 :将Promise的状态设置为fulfilled,并将传入的参数val赋值给this.value,用于后续.then()回调函数获取异步操作的结果。
  • 执行回调队列 :遍历onFulfilledCallbacks数组,依次执行其中的回调函数,并将this.value作为参数传入。如果在Promise处于pending状态时就调用了.then()方法,对应的成功回调函数会被存入onFulfilledCallbacks队列,在resolve执行时触发执行。

例如:

复制代码
const promise = new MyPromise((resolve, reject) => {
    setTimeout(() => {
        const data = { message: "操作成功" };
        resolve(data);
    }, 1000);
});

promise.then(result => {
    console.log(result);
});

上述代码中,1 秒后resolve被调用,传入对象{ message: "操作成功" }Promise状态变为fulfilledthis.value被赋值为该对象,随后.then()中的回调函数执行,输出该对象。

2.reject 函数的工作原理

reject函数的作用是将Promisepending状态转换为rejected状态,并处理传入的错误参数。当reject被调用时,参数传入后的执行流程如下:

  • 检查状态 :同样先判断Promise的状态是否为pending,只有pending状态下才会进行后续操作。
  • 更新状态和原因 :将Promise的状态设置为rejected,把传入的错误对象或原因err赋值给this.reason,用于后续.catch()回调函数捕获错误信息。
  • 执行回调队列 :遍历onRejectedCallbacks数组,依次执行其中的回调函数,并将this.reason作为参数传入。若在Promisepending时调用了.then()方法且提供了失败回调,或调用了.catch()方法,对应的回调函数会被存入onRejectedCallbacks队列,在reject执行时触发。

示例:

复制代码
const promise = new MyPromise((resolve, reject) => {
    setTimeout(() => {
        const error = new Error("操作失败");
        reject(error);
    }, 1500);
});

promise.catch(error => {
    console.error(error.message);
});

这里 1.5 秒后reject被调用,传入Error对象,Promise进入rejected状态,this.reason被赋值为该错误对象,.catch()中的回调函数捕获并输出错误信息。

3.特殊情况处理

  • resolve 传入 Promise 对象 :当resolve传入的参数是另一个Promise对象时,当前Promise的状态会跟随传入的Promise状态变化。在模拟实现的resolvePromise函数中,会对这种情况进行处理,等待传入的Promise状态确定后,再根据其结果决定当前Promisefulfilled还是rejected
  • resolve 传入带有 then 方法的对象 :如果resolve传入的参数是一个对象或函数,且包含then方法,会将其视为一个类Promise对象,调用其then方法,并根据then方法的执行结果来决定当前Promise的状态 。

三、resolve 和 reject的使用

1. resolve 函数详解

resolve函数用于将 Promise 从pending状态转换为fulfilled状态,意味着异步操作成功完成。当resolve被调用时,它接收的参数会作为 Promise 解决后的值,传递给后续.then()回调函数。

⑴.基本使用示例

复制代码
// 使用resolve将Promise置为fulfilled状态
const getSuccessMessage = new Promise((resolve, reject) => {
    setTimeout(() => {
        const message = "异步操作成功";
        resolve(message);
    }, 1000);
});

getSuccessMessage.then(result => {
    console.log(result);
});

在上述代码中,通过setTimeout模拟一个异步操作,1 秒后调用resolve函数,将字符串"异步操作成功"作为参数传入。此时,getSuccessMessage这个 Promise 进入fulfilled状态,.then()回调函数接收到该字符串并进行输出。

⑵.传入不同类型参数

resolve的参数可以是任意合法的 JavaScript 值,包括普通值、对象、数组,甚至是另一个 Promise 对象。

  • 传入普通值

    const numberPromise = new Promise((resolve, reject) => {
    resolve(42);
    });
    numberPromise.then(value => {
    console.log('接收到的数值:', value);
    });

传入对象

复制代码
const userPromise = new Promise((resolve, reject) => {
    const user = { name: "Alice", age: 30 };
    resolve(user);
});
userPromise.then(userInfo => {
    console.log('用户信息:', userInfo);
});
  • 传入 Promise 对象

    const innerPromise = new Promise((innerResolve) => {
    setTimeout(() => {
    innerResolve("内部Promise完成");
    }, 500);
    });

    const outerPromise = new Promise((resolve, reject) => {
    resolve(innerPromise);
    });

    outerPromise.then(result => {
    console.log(result);
    });

resolve传入另一个 Promise 对象时,外部 Promise 的状态会跟随内部 Promise 的状态变化,直到内部 Promise 状态确定,外部 Promise 才会完成状态转换。

2. reject 函数详解

reject函数用于将 Promise 从pending状态转换为rejected状态,表示异步操作失败。它接收的参数通常是一个错误对象,会传递给后续.catch()回调函数用于错误处理。

⑴.基本使用示例

复制代码
// 使用reject将Promise置为rejected状态
const getErrorMessage = new Promise((resolve, reject) => {
    setTimeout(() => {
        const error = new Error("异步操作失败");
        reject(error);
    }, 1500);
});

getErrorMessage.catch(error => {
    console.error(error.message);
});

这里同样使用setTimeout模拟异步操作,1.5 秒后调用reject函数,传入一个Error对象。getErrorMessage这个 Promise 进入rejected状态,.catch()回调函数捕获到错误信息并进行输出。

⑵.自定义错误类型

除了使用内置的Error对象,也可以自定义错误类型,以便更清晰地标识不同类型的错误。

复制代码
class CustomError extends Error {
    constructor(message) {
        super(message);
        this.name = "CustomError";
    }
}

const customPromise = new Promise((resolve, reject) => {
    setTimeout(() => {
        const customError = new CustomError("自定义错误发生");
        reject(customError);
    }, 800);
});

customPromise.catch(error => {
    if (error instanceof CustomError) {
        console.error('自定义错误:', error.message);
    } else {
        console.error('其他错误:', error.message);
    }
});

通过自定义错误类型,可以在捕获错误时进行更细致的处理,增强代码的健壮性和可维护性。

3. resolve 和 reject 的组合使用

在实际场景中,异步操作可能存在成功和失败两种情况,此时就需要合理使用resolvereject。以下是一个模拟网络请求的示例:

复制代码
function mockFetch(url) {
    return new Promise((resolve, reject) => {
        const isSuccess = Math.random() > 0.3; // 模拟70%的成功率
        setTimeout(() => {
            if (isSuccess) {
                const data = { message: `请求${url}成功` };
                resolve(data);
            } else {
                const error = new Error(`请求${url}失败`);
                reject(error);
            }
        }, 1000);
    });
}

mockFetch('https://example.com/api')
   .then(result => console.log(result))
   .catch(error => console.error(error.message));

mockFetch函数中,通过随机数模拟网络请求的成功或失败,根据结果调用resolvereject,后续通过.then().catch()分别处理成功和失败的情况。

四、Promise的方法

1. then () 方法

  • 语法promise.then(onFulfilled, onRejected)

  • 参数

    • onFulfilled:当 Promise 进入 fulfilled 状态时执行的回调函数,该函数接收 Promise 解决的值作为参数。该参数可选,如果不提供,Promise 解决的值会沿着链式调用向后传递 。
    • onRejected:当 Promise 进入 rejected 状态时执行的回调函数,该函数接收 Promise 被拒绝的原因作为参数。该参数可选,如果不提供,rejected 状态会传递到下一个.catch()中处理。
  • 示例

    getRandomNumber
    .then(value => console.log('成功获取随机数:', value))
    .catch(error => console.error('获取随机数失败:', error.message));

2. catch () 方法

  • 语法promise.catch(onRejected)

  • 参数onRejected是一个回调函数,与.then()方法中的onRejected作用相同,用于处理 Promise 被拒绝的情况,接收拒绝原因作为参数。

  • 示例

    getRandomNumber
    .then(value => {
    // 模拟后续可能失败的操作
    if (value < 0.8) {
    throw new Error('随机数不满足条件');
    }
    return value;
    })
    .catch(error => console.error('操作失败:', error.message));

3.finally () 方法

  • 语法promise.finally(onFinally)

  • 参数onFinally是一个回调函数,无论 Promise 最终是 fulfilled 还是 rejected 状态,该函数都会执行,且不接收任何参数。

  • 示例

    getRandomNumber
    .then(value => console.log('成功获取随机数:', value))
    .catch(error => console.error('获取随机数失败:', error.message))
    .finally(() => console.log('Promise操作已结束'));

4.Promise.all () 方法

  • 语法Promise.all(iterable)

  • 参数iterable是一个可迭代对象,比如数组,数组中的每个元素都应该是一个 Promise 对象。

  • 返回值与执行逻辑

    • iterable中的所有 Promise 都变为 fulfilled 状态时,Promise.all()返回的 Promise 才会进入 fulfilled 状态,其解决值是一个数组,数组元素按iterable中 Promise 的顺序对应每个 Promise 的解决值。
    • 只要iterable中有一个 Promise 进入 rejected 状态,Promise.all()返回的 Promise 就会立即进入 rejected 状态,其拒绝原因是第一个进入 rejected 状态的 Promise 的拒绝原因。
  • 示例

    const promise1 = new Promise((resolve) => setTimeout(() => resolve(1), 1000));
    const promise2 = new Promise((resolve) => setTimeout(() => resolve(2), 1500));
    const promise3 = new Promise((_, reject) => setTimeout(() => reject(new Error('Promise3失败')), 500));

    Promise.all([promise1, promise2, promise3])
    .then(values => console.log('所有Promise成功:', values))
    .catch(error => console.error('有Promise失败:', error.message));

5. Promise.race () 方法

  • 语法Promise.race(iterable)

  • 参数:同样接收一个可迭代对象,元素为 Promise 对象。

  • 返回值与执行逻辑Promise.race()返回的 Promise 会在iterable中任意一个 Promise 率先改变状态(无论是 fulfilled 还是 rejected)时,随之改变状态。其状态和值与率先改变状态的 Promise 一致。

  • 示例

    const promiseA = new Promise((resolve) => setTimeout(() => resolve('A完成'), 2000));
    const promiseB = new Promise((resolve) => setTimeout(() => resolve('B完成'), 1000));

    Promise.race([promiseA, promiseB])
    .then(result => console.log('率先完成的结果:', result));

6. Promise.resolve () 方法

  • 语法Promise.resolve(value)

  • 参数value可以是任意合法的 JavaScript 值,包括 Promise 对象、普通值。

  • 返回值与执行逻辑

    • 如果value是一个 Promise 对象,直接返回该 Promise 对象。
    • 如果value是一个普通值,返回一个新的已解决状态(fulfilled)的 Promise 对象,其解决值为value
  • 示例

    const resolvedPromise = Promise.resolve(42);
    const existingPromise = new Promise((resolve) => resolve('已存在的Promise'));

    resolvedPromise.then(value => console.log('普通值转换的Promise:', value));
    Promise.resolve(existingPromise).then(value => console.log('Promise转换的Promise:', value));

7.Promise.reject () 方法

  • 语法Promise.reject(reason)

  • 参数reason通常是一个错误对象,用于表示 Promise 被拒绝的原因。

  • 返回值与执行逻辑 :返回一个已拒绝状态(rejected)的 Promise 对象,其拒绝原因就是传入的reason参数。

  • 示例

    const rejectedPromise = Promise.reject(new Error('手动拒绝的Promise'));
    rejectedPromise.catch(error => console.error('Promise被拒绝:', error.message));

五、async await

asyncawait 是 JavaScript 里用于处理异步操作的语法糖,它们构建于 Promise 之上,能让异步代码以同步的形式书写,增强代码的可读性与可维护性。下面为你详细介绍:

1. async 函数

借助 async 关键字可定义异步函数,该函数会自动返回一个 Promise 对象。当函数执行完毕时,返回值会被包装进 Promise 的 resolve 中;若函数抛出异常,异常会被包装进 Promise 的 reject 中。

复制代码
async function exampleAsyncFunction() {
    return 'Hello, World!';
}

exampleAsyncFunction().then(result => {
    console.log(result); // 输出: Hello, World!
});

2.await 表达式

await 仅能在 async 函数内部使用。它会暂停 async 函数的执行,直至所等待的 Promise 被解决(resolved)或者被拒绝(rejected),之后才会恢复 async 函数的执行,并返回 Promise 的解决值。

复制代码
function delay(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

async function exampleWithAwait() {
    console.log('Before await');
    await delay(2000); // 等待 2 秒
    console.log('After await');
}

exampleWithAwait();

3. 处理错误

async 函数里,可以使用 try...catch 语句来处理 await 表达式可能出现的错误。

复制代码
function fetchData() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            reject(new Error('Data fetch failed'));
        }, 1000);
    });
}

async function handleError() {
    try {
        await fetchData();
    } catch (error) {
        console.error('Error:', error.message);
    }
}

handleError();

4. 并行处理

尽管 await 会暂停函数的执行,但你能够通过 Promise.all 来并行处理多个异步操作。

复制代码
function asyncTask1() {
    return new Promise(resolve => setTimeout(() => resolve('Task 1 completed'), 1000));
}

function asyncTask2() {
    return new Promise(resolve => setTimeout(() => resolve('Task 2 completed'), 1500));
}

async function parallelTasks() {
    const [result1, result2] = await Promise.all([asyncTask1(), asyncTask2()]);
    console.log(result1);
    console.log(result2);
}

parallelTasks();

通过 asyncawait,异步代码的编写和理解变得更加容易,就像同步代码一样直观。

相关推荐
一颗知足的心3 分钟前
Go语言之路————指针、结构体、方法
开发语言·后端·golang
yuanpan14 分钟前
C#如何正确的停止一个多线程Task?CancellationTokenSource 的用法。
开发语言·c#
程高兴16 分钟前
单相交直交变频电路设计——matlab仿真+4500字word报告
开发语言·matlab
夕水22 分钟前
这个提升效率宝藏级工具一定要收藏使用
前端·javascript·trae
会飞的鱼先生36 分钟前
vue3 内置组件KeepAlive的使用
前端·javascript·vue.js
斯~内克1 小时前
前端浏览器窗口交互完全指南:从基础操作到高级控制
前端
我真的不会C1 小时前
QT中的事件及其属性
开发语言·qt
Mike_jia1 小时前
Memos:知识工作者的理想开源笔记系统
前端
前端大白话1 小时前
前端崩溃瞬间救星!10 个 JavaScript 实战技巧大揭秘
前端·javascript
loveoobaby1 小时前
Shadertoy着色器移植到Three.js经验总结
前端