深入解析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,异步代码的编写和理解变得更加容易,就像同步代码一样直观。

相关推荐
在钱塘江10 分钟前
《你不知道的JavaScript-上卷》-笔记-5-作用域闭包
前端
搬砖码10 分钟前
Vue病历写回功能:实现多输入框内容插入与焦点管理🚀
前端
不简说15 分钟前
史诗级更新!sv-print虽然不是很强,但却是很能打的设计器组件
前端·产品
用户952511514015516 分钟前
最常用的JS加解密场景MD5
前端
Hilaku17 分钟前
“虚拟DOM”到底是什么?我们用300行代码来实现一个
前端·javascript·vue.js
阿猿收手吧!18 分钟前
【计算机网络】HTTP1.0 HTTP1.1 HTTP2.0 QUIC HTTP3 究极总结
开发语言·计算机网络
JAVA学习通19 分钟前
图书管理系统(完结版)
java·开发语言
打好高远球23 分钟前
mo契官网建设与SEO实践
前端
paishishaba26 分钟前
处理Web请求路径参数
java·开发语言·后端