Promise、 Async和Await详解

一、初识Promise

演示一个常见的异步编程误区 —— 在 setTimeout 回调中返回值无法被外层函数捕获。代码如下:

function fn(counter) {
    setTimeout(() => {
        if (counter > 0) {
            let total = 0;
            for (let i = 0; i <= counter; i++) {
                total += i;
            }
            return total   // ❗这个 return 只作用于箭头函数内部
        } else {
            return "你传递的数据不合法~"
        }
    }, 3000)
}

console.log(fn(100))

运行结果如下:

image

显示是undifined。

我们再改造一下代码,在代码中添加“return "ok"”,代码如下:

function fn(counter) {
    setTimeout(() => {
        if (counter > 0) {
            let total = 0;
            for (let i = 0; i <= counter; i++) {
                total += i;
            }
            return total   // ❗这个 return 只作用于箭头函数内部
        } else {
            return "你传递的数据不合法~"
        }
    }, 3000)

    return "ok"   // ✅ 这是 fn 函数实际返回的值
}

console.log(fn(100))

运行结果如下:

image

输出结果为OK。这是为什么呢?

🔍 关键问题解析:

1. setTimeout 是异步的

  • setTimeout 会将回调函数放入事件队列,等待指定时间(这里是 3000ms)后执行。
  • 而 fn() 函数本身是同步执行的,它会在设置完定时器后立即执行 return "ok",然后结束。

2. 回调中的 return 无效

  • 在 setTimeout 的回调函数里写的 return total 或 return "...",只是返回给 setTimeout 内部的匿名函数,并不会影响到 fn 函数的返回值,
    简单来说:setTimeout 内部的 return 只是返回给了定时器内部的匿名函数,而不是返回给外部的 fn 函数。
  • 所以无论 counter 是多少,fn(100) 都会立刻返回 "ok"
  • --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  • 继续对代码改造如下,用于演示 JavaScript 中的异步编程模式(以便于更好地理解 Promise 或 async/await)
  • function fn(counter, successCallback, failureCallback) {
        setTimeout(() => {
            if (counter > 0) {
                let total = 0;
                for (let i = 0; i <= counter; i++) {
                    total += i;
                }
                successCallback(total)
            } else {
                failureCallback("你传递的数据不合法")
            }
        }, 3000)
    
        return "ok"
    }
    
    fn(100, (value) => {
        console.log('执行成功了:', value);
    }, (reason) => {
        console.log('执行失败了:', reason);
    })

    运行结果如下:

  • image
  • 代码执行过程分析

     
    1. 第一步:调用函数 fn(100, ...)
       
      • 程序首先执行 fn(100, 成功回调, 失败回调) 这行代码
      • 进入 fn 函数内部,首先执行 setTimeout(注意:setTimeout 是异步任务,会被放到浏览器 / Node.js 的异步队列中,不会立即执行)
      • 然后函数 fn 直接执行 return "ok",这个返回值没有被接收 / 打印,所以不会有任何输出
       
    2. 第二步:等待 3 秒(异步任务执行)
       
      • 主线程代码执行完毕后,等待 3000ms(3 秒)
      • 3 秒后,setTimeout 中的回调函数开始执行:
        • 判断 counter > 0:传入的 counter 是 100,条件成立
        • 执行 for 循环计算 0 到 100 的累加和:total = 0+1+2+...+100 = 5050
        • 调用 successCallback(total),也就是执行传入的第一个箭头函数
        • 控制台输出:执行成功了: 5050
         
       
    3. 如果传入非法值(比如 fn(0, ...)
       
      • 3 秒后会执行 failureCallback,控制台输出:执行失败了: 你传递的数据不合法
       
     

    最终执行结果

     
    • 立即输出:无(fn 返回的 "ok" 没有被打印)
    • 3 秒后输出执行成功了: 5050
     

    补充说明(核心知识点)

     
    这段代码的关键是理解 异步任务(setTimeout)同步任务 的执行顺序:
     
    • setTimeout 里的代码是异步的,不会阻塞主线程
    • 函数 fn 会先执行完同步代码(return "ok"),再等 3 秒执行异步代码(计算累加和 + 回调)
     

    总结

     
    1. 函数 fn 调用后会立即返回 "ok",但该返回值未被使用,因此无即时输出;
    2. 3 秒后异步逻辑执行,因传入的 counter=100 合法,最终控制台输出 执行成功了: 5050
    3. 核心逻辑是异步任务(setTimeout)的执行时机晚于同步代码,且回调函数会接收计算结果。
  • ***************************************************************************************************
  • ✅ 如果希望这段代码真正“工作”,改用 Promise 模式!
  • function fn(counter) {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                if (counter > 0) {
                    let total = 0;
                    for (let i = 0; i <= counter; i++) {
                        total += i;
                    }
                    resolve(total);
                } else {
                    reject("你传递的数据不合法~");
                }
            }, 3000);
        });
    }
    
    // 调用方式:
    fn(100).then(result => console.log(result),
        err => console.log(err));

    运行结果如下:

  • image
  • 或者,promise模式的另一种写法,代码如下:
  • function fn(counter) {
        let p = new Promise((resolve, reject) => {
            setTimeout(() => {
                if (counter > 0) {
                    let total = 0;
                    for (let i = 0; i <= counter; i++) {
                        total += i;
                    }
                    resolve(total);
                } else {
                    reject("你传递的数据不合法~");
                }
            }, 3000);
        });
        return p;
    }
    
    // 调用方式:
    fn(100).then(result => console.log(result)).catch(err => console.log(err));

    运行结果如下:

  • image
  • 二、resolve的实参问题
  • resolve 是 Promise 构造函数中执行器(executor)的第一个参数,它是一个函数,调用时传入的实参会决定当前 Promise 的最终状态和后续 .then() 回调的入参。下面从基础到进阶,详细说明 resolve 实参的不同情况:
     

    1. 基础情况:传入普通值(非 Promise/thenable)

     
    如果 resolve 的实参是普通值(数字、字符串、对象、null、undefined 等),Promise 会立即进入 fulfilled(已完成)状态,这个值会作为后续 .then() 第一个回调函数的参数。
     
    示例代码
  • // 传入普通字符串
    const p1 = new Promise((resolve) => {
      resolve("成功的结果"); // resolve 实参为普通字符串
    });
    
    p1.then((result) => {
      console.log(result); // 输出:成功的结果
    });
    
    // 传入普通对象
    const p2 = new Promise((resolve) => {
      resolve({ code: 200, data: [1, 2, 3] }); // resolve 实参为对象
    });
    
    p2.then((res) => {
      console.log(res.data); // 输出:[1, 2, 3]
    });

    运行结果如下:

  • image
  • 2. 特殊情况 1:传入另一个 Promise 实例

     
    如果 resolve 的实参是另一个 Promise 实例,当前 Promise 会 “接管” 这个实例的状态:
     
    • 若传入的 Promise 已完成,当前 Promise 也完成,取其结果;
    • 若传入的 Promise 已拒绝,当前 Promise 也拒绝,取其错误;
    • 若传入的 Promise 未决(pending),当前 Promise 会等待其状态确定。
     
    示例代码
  • // 先定义一个待完成的 Promise
    const p3 = new Promise((resolve) => {
      setTimeout(() => resolve("p3 的结果"), 1000);
    });
    
    // resolve 传入 p3(Promise 实例)
    const p4 = new Promise((resolve) => {
      resolve(p3); // 实参是另一个 Promise
    });
    
    p4.then((result) => {
      console.log(result); // 1秒后输出:p3 的结果
    });
    
    // 传入已拒绝的 Promise
    const p5 = Promise.reject("出错了");
    const p6 = new Promise((resolve) => {
      resolve(p5);
    });
    
    p6.catch((err) => {
      console.log(err); // 输出:出错了
    });

    运行结果如下:

  • image
  • 3. 特殊情况 2:传入 thenable 对象(有 .then () 方法的对象)

     
    如果 resolve 的实参是具有 .then () 方法的对象 / 函数(称为 thenable),Promise 会自动执行这个 .then() 方法,并传入自己的 resolve 和 reject 作为参数,相当于 “同化” 这个 thenable 的行为。
     
    示例代码
  • // 定义一个 thenable 对象
    const thenableObj = {
      then: function (resolve, reject) {
        setTimeout(() => resolve("thenable 的结果"), 1500);
      },
    };
    
    const p7 = new Promise((resolve) => {
      resolve(thenableObj); // 实参是 thenable 对象
    });
    
    p7.then((result) => {
      console.log(result); // 1.5秒后输出:thenable 的结果
    });

    再学习一个.then()方法的示例,这是ES6 方法简写模式,代码如下:

  • let promise = new Promise((resolve,reject)=>{
        resolve(
            {then(resolve,reject){
                resolve("this is thenable!")
                }}
        );
    });
    
    promise.then((value)=>{
        console.log("value is:",value);
    })

     

  • 对象字面量(最常用)与ES6方法简写模式是等价的,结果是一样的,如下所示
  • let promise = new Promise((resolve, reject) => {
        resolve({
            then: function(resolve, reject) { // 这里是对象的属性 key: value
                resolve("this is thenable!");
            }
        });
    });
    
    promise.then((value) => {
        console.log("value is:", value); 
        // 输出: value is: this is thenable!
    });
  • 运行结果如下:

  • image
  • 更改一下上述代码,具体如下:
  • let promise = new Promise((resolve,reject)=>{
        resolve(
           function  then(resolve,reject){
                resolve("this is thenable!")
                }
        );
    });
    
    promise.then((value)=>{
        console.log("value is:",value);
    })
  • 输出的结果却是
  • image
  • 详细分析

    1. 为什么逻辑失效了?

    Promise 的 resolve 函数在处理传入值时,遵循以下规则:
    • 如果传入的是一个对象(Object)或函数(Function),它会检查该值是否有名为 then 的属性(Property),且该属性必须是一个函数。
    • 如果有,它就把这个值当作 "Thenable" 处理,调用它的 then 方法。
    • 如果没有,它就把它当作普通值直接 resolve。
    • resolve(
          function then(resolve, reject) { ... }
      );

      在上述代码中,

      传入 resolve 的是一个函数对象
      1. ❤️这个函数对象的名字叫 then(这是函数的名称 name 属性)。
      2. ❤️但是,这个函数对象本身并没有一个名为 then 的属性
      JavaScript 引擎检查的是:value.then 是否存在?
      • 🔯对于一个普通的函数 function then() {}value.then 是 undefined
      • 🔯因此,Promise 认为这不是一个 Thenable 对象,而是一个普通的函数值。
      • 2. 实际运行结果

        代码会执行,但流程如下:
        1. 👉new Promise 立即调用 resolve,传入了一个函数。
        2. 👉Promise 检测到该函数没有 .then 属性,于是直接将这个函数作为最终结果。
        3. 👉promise.then((value) => ...) 被触发。
        4. 👉value 是这个函数对象本身。
        控制台输出示例:
      • value is: [Function: then]

        *******************************************************************************************************************

      • 4. 特殊情况 3:传入 undefined(或无实参)

         
        如果调用 resolve 时没有传参,或显式传入 undefined,Promise 仍会进入 fulfilled 状态,.then() 回调的参数为 undefined
         
        示例代码
      • const p8 = new Promise((resolve) => {
          resolve(); // 无实参,等价于 resolve(undefined)
        });
        
        p8.then((result) => {
          console.log(result); // 输出:undefined
        });

        运行结果如下:

      • image
posted @ 2026-03-09 16:48  chenlight  阅读(1)  评论(0)    收藏  举报