回调地狱

针对以下代码中的回调函数层层嵌套的关系,形成了回调地狱

function confession(name, onfulfilled, onrejected) {
    console.log('已发送邀请,请等待回复...');
    setTimeout(() => {
        if (0.3 >= Math.random()) {
            onfulfilled(`恭喜${name},你被接受了~`);
        } else {
            onrejected(`抱歉${name},你被拒绝了...`);
        }
    }, 1000);
}

confession('王哥', (reply) => {
    console.log('成功', reply);
}, (reply) => {
    console.log('失败', reply);
    confession('孙哥', (reply) => {
        console.log('成功', reply);
    }, (reply) => {
        console.log('失败', reply);
        confession('张哥', (reply) => {
            console.log('成功', reply);
        }, (reply) => {
            console.log('失败', reply);
            confession('赵哥', (reply) => {
                console.log('成功', reply);
            }, (reply) => {
                console.log('失败', reply);
            })
        })
    })
})

若要解决这个问题就需要 Promise规范

Promise规范

Pomise是一个专门处理异步场景的规范,用来避免回调地狱的产生,使异步代码更加的简洁高效。

每一个promise对象都有两个阶段三个状态。

两个阶段:未决阶段 unsettled、已决阶段 settled

  • 第一阶段 (Unsettled)pending
  • 第二阶段 (Settled)fulfilled 或 rejected

三个状态:挂起状态 pending、完成状态 fulfilled、失败状态 rejected

  1. pending:初始状态,既不是成功,也不是失败。对应“进行中”阶段。
  2. fulfilled:操作成功完成。当 resolve 函数被调用时,Promise 的状态会从 pending 变为 fulfilled。对应“已确定”阶段中的成功情况。
  3. rejected:操作失败。当 reject 函数被调用或执行器函数内部抛出异常时,Promise 的状态会从 pending 变为 rejected。对应“已确定”阶段中的失败情况。

Promise 核心特性——状态的不可逆性

为了让这段描述在技术层面更加严谨,同时在逻辑上更易懂,我们可以将其整合优化为以下两条核心准则:

1. 状态流转的单向性(时间不能倒流)

Promise 实例在生命周期内,状态只能发生一次单向流转。它总是从初始的等待态(pending)出发,最终变为成功态(fulfilled)失败态(rejected)。这个过程是绝对单向的,不存在任何“逆行”或“回退”的可能。

2. 结果的不可篡改性(历史不可改写)

一旦 Promise 的状态从pending变为 fulfilled或 rejected,它的状态就会被永久锁定(固化)。无论后续代码如何尝试再次调用resolve或reject,都无法改变已经确定的结果。

Promise链式调用

catch方法

.catch(onRejected) 等价于 .then(null, onRejected)

新 Promise 的状态由什么决定?

规则一:什么都不做 → 原样传递

如果 .then() 里面什么都不写或者没有对应的处理函数

  • 如果上游是成功 → 新 Promise 也是成功,数据和上游一样
  • 如果上游是失败 → 新 Promise 也是失败,错误和上游一样
// 上游成功时
Promise.resolve("原始数据").then().then(data => {
  console.log(data); // "原始数据" - 数据原样传递
});

// 上游失败时  
Promise.reject("错误信息").then().catch(error => {
  console.log(error); // "错误信息" - 错误原样传递
});
规则二:正常处理 → 用返回值更新

如果 .then() 里的回调函数正常执行完毕(没有抛出错误):

  • 有返回值 → 新 Promise 成功,数据是这个返回值
  • 没有返回值 (undefined) → 新 Promise 成功,数据是 undefined
Promise.resolve("Hello")
  .then(data => data + " World")  // 返回 "Hello World"
  .then(result => {
    console.log(result); // "Hello World"
    return "Next Value";         // 返回新值
  })
  .then(final => {
    console.log(final); // "Next Value"
  });
规则三:处理出错 → 转为失败

如果 .then() 里的回调函数执行过程中抛出了错误

  • 新 Promise 变为失败状态,错误信息就是抛出的内容
Promise.resolve("OK")
  .then(data => {
    throw new Error("出错了!"); // 抛出错误
  })
  .catch(error => {
    console.log(error.message); // "出错了!"
  });
规则四:返回 Promise → 等待其结果

如果 .then() 里的回调函数返回另一个 Promise 对象

  • 新 Promise 会等待这个返回的 Promise 完成,然后采用它的最终状态和数据
    Promise.resolve("Start")
      .then(data => {
        // 返回一个异步操作
        return fetch("/api/data"); // 这是一个 Promise
      })
      .then(response => {
        // 这里的 response 是上面 fetch 成功后的结果
        return response.json();
      })
      .catch(error => {
        // 这里可能捕获上面任何一个步骤的错误
      });

    代码示例:

    new Promise((resolve, reject) => {
      throw new Error(1);  //抛出错误,为rejected状态
    })
      .then((res) => {
        console.log(res);
        return new Error('2'); //未对rejected进行处理,仍然为rejected Error(1)
      })
      .catch((err) => {
        throw err;
        return 3; //对rejected进行处理,处理中又抛出错误,状态为rejected Error(1)
      })
      .then((res) => {
        console.log(res);
      }); //未对rejected进行处理,仍然为rejected Error(1)
    
    

    async和await关键字

    async关键字用于修饰函数,被它修饰的函数,返回的值为Promise

    async function foo() {
      // 没有 await,没有 return,没有 throw
    }
    const p = foo();
    console.log(p);  // Promise { <fulfilled>: undefined }
    
    
    async function method(){
      throw new Error(1); // 若执行过程报错,则任务是rejected
    }
    
    method(); // Promise { <rejected> Error(1) }

    async函数会自动返回一个 Promise,且正常执行完毕时自动变为 fulfilled,值为undefined,不需要手动调用resolve。

    async函数需要手动进行调用执行,不像 new Promise((resolve, reject) = > {}) 可以直接执行。

    await关键字表示等待某个Promise完成,它必须用于async函数中

    1. await 必须用于 async 函数中

    // ❌ 错误:在非 async 函数中使用 await
    function normalFunction() {
      const result = await Promise.resolve(1); // SyntaxError: await is only valid in async functions
    }
    
    // ✅ 正确:在 async 函数中使用 await
    async function asyncFunction() {
      const result = await Promise.resolve(1);
      console.log(result); // 输出: 1
    }
    asyncFunction();

    2. await 也可以等待其他数据(非 Promise 值)

    async function handleVariousValues() {
      // 等待数字
      const num = await 42;
      console.log('数字:', num); // 输出: 数字: 42
      
      // 等待字符串
      const str = await "hello";
      console.log('字符串:', str); // 输出: 字符串: hello
      
      // 等待对象
      const obj = await { name: "Alice" };
      console.log('对象:', obj); // 输出: 对象: { name: 'Alice' }
      
      // 等待 undefined
      const undef = await undefined;
      console.log('undefined:', undef); // 输出: undefined: undefined
    }
    
    handleVariousValues();

    3. 使用 try-catch 处理失败任务

    async function fetchData() {
      try {
        // 成功场景
        const successData = await Promise.resolve("成功数据");
        console.log('成功:', successData); // 输出: 成功: 成功数据
        
        // 失败场景 - 故意抛出错误
        await Promise.reject(new Error("网络请求失败"));
      } catch (error) {
        // 捕获并处理错误
        console.error('捕获错误:', error.message); // 输出: 捕获错误: 网络请求失败
        return "默认数据"; // 可以返回默认值
      } finally {
        console.log('清理工作完成');
      }
    }
    
    fetchData().then(result => {
      console.log('最终结果:', result); // 输出: 最终结果: 默认数据
    });

    await在执行上下文的时候,先执行await对应的Promise函数,若Promise返回值为fulfilled或是rejected,那么await会先进入微序列,若返回值为pending则先不进入微序列。

    Logo

    AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

    更多推荐