JavaScript-异步编程



一、日常前端开发使用的异步编程方法

  • 回调函数
  • 事件监听
  • Promise
  • Generator
  • async/await

二、同步编程和异步编程的区别

1、什么是同步

同步是执行某段代码时,在该代码没有得到返回结果前,其他代码是无法执行的,但是一旦拿到返回值,就可以执行其他的代码

2、什么是异步

异步就是当某段代码执行异步过程调用发出后,这段代码不会立刻得到返回结果,而是在异步调用发出之后,一般通过回调函数处理这个调用之后拿到的结果

  • 为什么需要异步

JavaScript是单线程的,如果JS都是同步执行可能造成阻塞;如果使用异步就不会阻塞,我们不需要等待异步代码执行的返回结果,可以继续执行该异步任务之后的代码逻辑

三、异步实现

1、回调函数

在 JavaScript 中,回调函数是一种通过函数参数传递的函数,这个函数将在某个操作完成或某个事件触发时被调用。

回调实现异步的场景:

  • ajax请求的回调

          function fetchData(url, callback) {
            const xhr = new XMLHttpRequest();
            xhr.open(`GET`, url);
            xhr.onreadystatechange = function () {
              if (xhr.readyState === 4 && xhr.status === 200) {
                callback(xhr.responseText);
              }
            };
            xhr.send();
          }
    
          function handleResponse(data) {
            console.log(`获取的数据:`, data);
          }
          fetchData(
            "http://127.0.0.1:4523/m1/6693534-6403130-default/user/info",
            handleResponse
          );
    

    fetchData 函数发起了一个 HTTP GET 请求。在请求完成后,onreadystatechange 事件触发并检查请求状态,如果请求成功,那么回调函数 handleResponse 就会被调用并接收响应数据。这种模式下,回调函数的作用就是在异步操作完成时处理结果。

  • 定时器中的回调

    function doSomethingAsync(callback) {
        setTimeout(() => {
            console.log(`异步操作完成`);
            callback();
        }, 1000);
    }
    
    function onComplete() {
        console.log(`回调函数被调用`);
    }
    
    doSomethingAsync(onComplete);
    

    在这个例子中,doSomethingAsync 函数执行一个模拟的异步操作(通过 setTimeout 模拟),在这个操作完成之后,callback 函数会被调用。在这里,onComplete 函数就是作为回调函数传递给 doSomethingAsync 函数的。

  • 事件监听

     document.getElementById(`myButton`).addEventListener('click', function() {
            console.log('按钮被点击了')
          })
    

    在这里,addEventListener 方法注册了一个回调函数,该函数将在 myButton 按钮被点击时执行。这个回调函数是异步的,因为它仅在特定的用户操作(即点击事件)发生后才会被调用。

  • Nodejs中的一些方法的回调

  • 问题:回调层级过多,陷入回调地狱

    doSomethingAsync(function() {
        doSomethingElseAsync(function() {
            doAnotherThingAsync(function() {
                console.log(`所有异步操作完成`);
            });
        });
    });
    

2、解决回调地狱

方法1:Promise相关

Promise 是一个对象,代表一个异步操作的最终完成(或失败)及其结果值。它有三种状态:pending:初始状态,既不是成功也不是失败。fulfilled:操作成功完成。rejected:操作失败。

  • 基本语法new Promise((resolve, reject) => {...})

  • 状态流转:pending → fulfilled/rejected

  • 处理方法.then().catch().finally()

  • 静态方法

    1. Promise.all:并行执行多个 Promise,全部成功时返回结果数组,任一失败则立即终止。
    2. Promise.race:并行执行多个 Promise,第一个完成(无论成功或失败)的结果作为最终结果。
    3. Promise.allSettled:并行执行多个 Promise,返回所有 Promise 的结果(无论成功或失败)。
  • 使用Promise

    function doSomethingAsync() {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                console.log('异步操作1完成');
                resolve();
            }, 1000);
        });
    }
    
    function doSomethingElseAsync() {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                console.log('异步操作2完成');
                resolve();
            }, 1000);
        });
    }
    
    function doAnotherThingAsync() {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                console.log('异步操作3完成');
                resolve();
            }, 1000);
        });
    }
    
    // 使用 Promise 链式调用(顺序执行)
    doSomethingAsync()
        .then(() => doSomethingElseAsync())
        .then(() => doAnotherThingAsync())
        .then(() => {
            console.log('所有异步操作完成');
        })
        .catch(error => {
            console.log('发生错误:', error);
        });
    

    doSomethingAsync 函数返回一个 Promise 对象,表示异步操作的结果。then 方法用于处理操作成功的情况,而 catch 方法用于处理失败情况。通过这种方式,我们可以避免回调地狱的问题,并且代码更具可读性。

  • 使用Promise.all

    function doSomethingAsync() {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                console.log('异步操作1完成');
                resolve();
            }, 1000);
        });
    }
    
    function doSomethingElseAsync() {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                console.log('异步操作2完成');
                resolve();
            }, 1000);
        });
    }
    
    function doAnotherThingAsync() {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                console.log('异步操作3完成');
                resolve();
            }, 1000);
        });
    }
    
    // 使用 Promise.all 并行执行所有异步操作
    Promise.all([
        doSomethingAsync(),
        doSomethingElseAsync(),
        doAnotherThingAsync()
    ])
    .then(() => {
        console.log('所有异步操作完成');
    })
    .catch(error => {
        console.log('发生错误:', error);
    });
    

    对比:如果需要顺序执行异步操作,应该继续使用链式调用;如果希望并行执行以提高效率,则适合使用 Promise.all

    方法2:Generator

    ES6引入,最大的特点就是可以交出函数的执行权,Generate函数可以看出是异步任务的容器,需要暂停的地方,都有yield语法进行标注,最后返回的是迭代器

          function doSomethingAsync(callback) {
            setTimeout(() => {
              console.log("异步操作1完成");
              callback(null); // 传递 null 表示无错误
            }, 1000);
          }
    
          function doSomethingElseAsync(callback) {
            setTimeout(() => {
              console.log("异步操作2完成");
              callback(null);
            }, 1000);
          }
    
          function doAnotherThingAsync(callback) {
            setTimeout(() => {
              console.log("异步操作3完成");
              callback(null);
            }, 1000);
          }
    
          // 定义 Generator 函数(控制流程)
          function* asyncFlow() {
            let error;
    
            // 执行第一个异步操作
            error = yield (nextCallback) => {
              doSomethingAsync((err) => nextCallback(err));
            };
            if (error) throw new Error(error);
    
            // 执行第二个异步操作
            error = yield (nextCallback) => {
              doSomethingElseAsync((err) => nextCallback(err));
            };
            if (error) throw new Error(error);
    
            // 执行第三个异步操作
            error = yield (nextCallback) => {
              doAnotherThingAsync((err) => nextCallback(err));
            };
            if (error) throw new Error(error);
    
            console.log("所有异步操作完成");
          }
    
          // 执行器函数(手动迭代 Generator)
          const iterator = asyncFlow();
    
          function iterate(iteration) {
            if (iteration.done) return; // 完成迭代
    
            const callbackGenerator = iteration.value;
    
            // 执行回调函数,处理异步结果
            callbackGenerator((error) => {
              if (error) {
                try {
                  iterator.throw(error); // 抛错误到 Generator
                } catch (err) {
                  console.log("发生错误:", err.message);
                }
                return;
              }
              // 无错误,继续下一步
              iterate(iterator.next());
            });
          }
          // 启动流程
          iterate(iterator.next());
    
    • 区别

      • Promise 是专为异步操作设计的抽象,封装了异步状态(pending/fulfilled/rejected),通过 .then()/.catch() 链式调用处理结果。
      • Generator 是迭代器,本身并非异步,但可通过 yield 暂停 / 恢复机制控制异步流程,需配合执行器使用。
    • Promise 适用场景

      • 大多数异步操作(如 HTTP 请求、文件 I/O)。
      • 需要并行执行多个异步任务(Promise.all)。
      • 与现代框架(React、Vue)和 API(fetch)无缝集成。
    • Generator 适用场景

      • 需要精细控制异步流程(如状态机、数据流处理)。
      • 实现自定义迭代器或异步生成器(如生成无限数据流)。
    • Promise 的局限性

      • 链式调用深层嵌套时仍可能导致回调地狱(尽管比传统回调轻量)。
      • 一旦创建立即执行,无法中途取消。
    • Generator 的优势

      • 可通过 yield 实现惰性求值(如按需生成数据)。
      • 结合 co 库等执行器,可写出类似同步的异步代码。
    方法3:async/await

    async是Generator函数的语法糖,async 函数返回一个 Promise,而 await 关键字可以暂停 async 函数的执行,等待 Promise 解决。

    async function performTasks() {
        try {
            await doSomethingAsync();
            await doSomethingElseAsync();
            await doAnotherThingAsync();
            console.log(`所有异步操作完成`);
        } catch (error) {
            console.error(`发生错误: `, error);
        }
    }
    
    performTasks();
    

    performTasks 是一个 async 函数,它内部的异步操作通过 await 关键字来依次执行。这样写的好处在于代码结构更加清晰,易于理解,并且无需通过回调函数进行层层嵌套。

四、总结

总结来看,JavaScript 通过回调函数实现了强大的异步编程能力。回调函数在许多场景中得到了广泛的应用,如网络请求、事件处理和定时器操作。尽管回调函数有其局限性,特别是在处理复杂的异步操作时容易导致回调地狱,但通过合理的设计和使用现代的异步处理方式如 Promiseasync/await,我们可以有效地避免这些问题并编写出简洁、可维护的异步代码。

你可能感兴趣的:(javascript,开发语言,前端)