ES6 Promise:JavaScript异步操作的巅峰之选(一)

164 阅读9分钟

一、异步

1.异步的基本概念

在我们了解promise之前,我们首先要了解 异步 的概念。

异步(Asynchronous):

  • 是一种编程模式,指的是程序执行过程中,某些操作不需要等待其他操作完成就可以继续进行。在异步编程中,任务的执行顺序不是按照代码的书写顺序来确定的,而是由程序运行时环境来决定。

我们来举个例子:

        function a() {
            setTimeout(function () {
                console.log('喜羊羊吃火锅');
            }, 1000)
        }
        a()
        function b() {
            console.log('喜羊羊在刷抖音');
        }

        b()

在这个例子中,很明显,a是一个定时器函数,耗时1秒,b是即刻打印。 那么大家可以根据异步的概念来猜一下,a 和 b 是谁先打印呢?

没错!相信大家都有答案了,实际上是 b 先打印,1秒后才打印 a,这也就是异步的处理了。

屏幕截图 2023-11-14 130523.png

2.异步的优点

因此我们知道,异步编程通常用于处理可能耗时的任务,比如网络请求、文件读写、定时器等。在这些情况下,如果程序等待这些任务完成,会导致阻塞,降低程序的性能和响应速度。通过异步编程,程序可以在执行这些任务的同时继续执行后续代码,提高了程序的效率。

异步编程通常用于处理可能耗时的任务,比如网络请求、文件读写、定时器等。在这些情况下,如果程序等待这些任务完成,会导致阻塞,降低程序的性能和响应速度。通过异步编程,程序可以在执行这些任务的同时继续执行后续代码,提高了程序的效率。这就是异步的优点。

3.异步的弊端

但是程序的效率异步确实给我们提高了,但是有的时候需求要我们必须先执行第一项,再去执行其他的,这样要怎么处理呢?

一般而言,我们用回调函数来处理:

function a(cb) {
    setTimeout(() => {
        console.log('木木');
        cb()
    }, 1000)
}

function b() {
    setTimeout(() => {
        console.log('清华大学');
    }, 500)
}
a(b)

这个例子中,在原始状态下,异步编程之下应该实现打印快的那个,也就是先打印清华大学,再打印木木对不对?

现在假设我们必须要先拿到用户姓名,然后才能拿到用户学校,我们采用回调函数的方法,直接在函数a中调用b,可以满足我们的想法。

屏幕截图 2023-11-14 133228.png

那这里就有问题了,使用回调函数处理多个嵌套的异步操作时,代码可能会变得非常混乱,形成所谓的“回调地狱”。这使得代码难以理解和维护,可读性降低。

回调地狱(Callback Hell):

  • 问题: 在多个嵌套的回调函数中编写代码,使得代码难以阅读和维护。

1> 回调地狱(Callback Hell)

想想看,假设我们有三个异步任务:task1task2task3,它们需要按顺序执行。在回调函数中,代码可能会看起来像这样:

task1((result1) => {
  console.log(result1);
  task2((result2) => {
    console.log(result2);
    task3((result3) => {
      console.log(result3);
      // 后续操作...
    });
  });
});

这还只是三个任务,代码已经有点复杂了,那如果是十个、百个...甚至一千个呢?

2> 并发控制

另外,使用传统的回调函数,我们可能会陷入并发控制的问题。比如,我们希望等待task1task2都完成后执行task3

task1((result1) => {
  console.log(result1);
  task2((result2) => {
    console.log(result2);
    // 等待task1和task2完成后执行task3
    task3((result3) => {
      console.log(result3);
      // 后续操作...
    });
  });
});

3> 错误处理

错误处理在回调中可能会变得混乱,错误可能被传递到回调链的某个地方,难以捕获和处理。

task1((result1, error1) => {
  if (error1) {
    console.error(error1);
  } else {
    console.log(result1);
    task2((result2, error2) => {
      if (error2) {
        console.error(error2);
      } else {
        console.log(result2);
        task3((result3, error3) => {
          if (error3) {
            console.error(error3);
          } else {
            console.log(result3);
            // 后续操作...
          }
        });
      }
    });
  }
});

这就是异步编程的弊端了,当我们要处理多个复杂任务时,异步会让代码变得难以理解且可读性低,那么我们可以用什么方法解决回调地狱,让我们写的代码既简单又容易理解呢?

接下来!就要有请我们今天的主角——Promise出场了!

二、Promise

1.Promise的基本语法

1> 什么是Promise?

Promise 是 JavaScript 中用于处理异步操作的对象。它是一种为了更清晰和更结构化地处理异步代码而引入的编程模型。Promise 对象代表一个尚未完成并且可能在未来完成的操作,可以是一个异步操作,也可以是一个同步操作。

2> Promise的三种状态

  1. Pending(进行中): 初始状态,表示操作尚未完成,处于进行中。
  2. Fulfilled(已完成): 表示操作成功完成。
  3. Rejected(已失败): 表示操作失败。

3> Promise的基本用法

创建一个 Promise 对象的基本语法如下:

const promise = new Promise((resolve, reject) => {
  // 异步操作,比如网络请求、文件读写等
  // 如果操作成功,调用 resolve,并传递结果
  // 如果操作失败,调用 reject,并传递错误信息
});

4> Promise 的主要特点

  • 链式调用: 可以使用 .then() 方法处理异步操作的结果,从而形成链式调用,使代码更加清晰。

    promise.then(
      (result) => {
        // 处理操作成功的情况
        console.log(result);
      },
      (error) => {
        // 处理操作失败的情况
        console.error(error);
      }
    );
    
  • 异步错误处理: 使用 .catch() 方法捕获 Promise 链中的任何错误。

    promise
      .then((result) => {
        // 处理操作成功的情况
        console.log(result);
      })
      .catch((error) => {
        // 处理操作失败的情况
        console.error(error);
      });
    
  • Promise.all(): 处理多个 Promise 对象,等待所有 Promise 完成。

    const promises = [promise1, promise2, promise3];
    
    Promise.all(promises)
      .then((results) => {
        // 处理所有 Promise 成功完成的情况
        console.log(results);
      })
      .catch((error) => {
        // 处理任何一个 Promise 失败的情况
        console.error(error);
      });
    

Promise 的引入旨在解决回调地狱(Callback Hell) 和提高异步代码的可读性。后续的 ECMAScript 版本引入了 async/await 语法,进一步简化了异步代码的书写。

5> 举例

这里举个很好理解的例子:

function breakfast() {
  return new Promise((resolve, reject) => {
      setTimeout(() => {
          console.log('吃早饭');
          resolve('吃完早饭啦!')
      }, 2000)
  })
}
function lunch() {
  return new Promise((resolve, reject) => {
      setTimeout(() => {
          console.log('吃中饭');
          resolve('吃完中饭啦!')
      }, 1000)
  })
}
function dinner() {
  return new Promise((resolve, reject) => {
      setTimeout(() => {
          console.log('吃晚饭');
          resolve('吃完晚饭啦!')
      }, 500)
  })
}

breakfast()
  .then((res) => {
      console.log(res);
      return lunch()
  })
  .then(res2 => {
      console.log(res2);
      return dinner()
  })

这里的一段代码使用了 Promise 来模拟一天中的吃饭过程,并展示了如何通过 Promise 的链式调用处理异步操作。

  1. 在这个链式调用中,breakfast() 返回的 Promise 被解析为 Fulfilled 后,.then((res) => { ... }) 中的回调函数被执行,打印出 '吃完早饭啦!',然后返回 lunch() 函数的 Promise。

  2. 接着,当 lunch() 完成后,.then(res2 => { ... }) 中的回调函数被执行,打印出 '吃完中饭啦!',并返回 dinner() 函数的 Promise。

  3. 最后,当 dinner() 完成后,整个 Promise 链结束。

这种链式调用的方式使得异步操作的代码看起来更加清晰和顺序,避免了回调地狱,提高了可读性。整体执行流程是按照顺序执行三顿饭的操作,每一顿饭的完成都触发了下一顿饭的开始。

2. Promise的状态转换

Promise 对象有三个状态:Pending(进行中)、Fulfilled(已完成)、Rejected(已失败)。一个 Promise 对象的状态可以从 Pending 转换为 Fulfilled 或 Rejected,但一旦状态被转换,就不可再次修改。以下是 Promise 的状态转换过程:

1> Pending(进行中)

Promise 对象初始时处于 Pending 状态,表示操作尚未完成。

const promise = new Promise((resolve, reject) => {
  // 异步操作
});

2> Fulfilled(已完成)

如果异步操作成功,调用 resolve(result) 将 Promise 的状态从 Pending 转换为 Fulfilled。

const promise = new Promise((resolve, reject) => {
  // 异步操作成功
  resolve("操作成功");
});

3> Rejected(已失败)

如果异步操作失败,调用 reject(error) 将 Promise 的状态从 Pending 转换为 Rejected。

const promise = new Promise((resolve, reject) => {
  // 异步操作失败
  reject("操作失败");
});

4> 示例:

const examplePromise = new Promise((resolve, reject) => {
  const isSuccess = true; // 模拟异步操作成功或失败

  if (isSuccess) {
    resolve("操作成功");
  } else {
    reject("操作失败");
  }
});

// 使用 .then() 处理 Fulfilled 状态
examplePromise.then(
  (result) => {
    console.log("Fulfilled:", result);
  },
  (error) => {
    console.error("Rejected:", error);
  }
);

在这个示例中,examplePromise 创建时处于 Pending 状态,根据 isSuccess 的值,可能会转换为 Fulfilled 或 Rejected 状态。根据状态的不同,分别执行了对应的回调函数。

Promise 对象的状态转换是一次性的,一旦状态转换完成,就无法再次修改。这种特性使得 Promise 更容易管理异步操作的状态和结果。

三、总结

在前面的讨论中,我们了解了关于Promise和异步编程的一系列解释和示例。以下是主要内容的总结:

  1. 异步编程:

    • 异步编程是一种编程模式,允许程序在执行耗时的操作时继续执行后续代码,而不必等待操作完成。
    • JavaScript中的异步编程常用于处理网络请求、文件读写、定时器等需要等待的操作。
  2. 回调函数:

    • 回调函数是一种处理异步操作的传统方式,通过将函数作为参数传递给另一个函数,在异步操作完成时调用该函数。
  3. Promise:

    • Promise是JavaScript中处理异步操作的对象,提供了更清晰、更结构化的方式来处理异步代码。
    • Promise对象有三个状态:Pending(进行中)、Fulfilled(已完成)、Rejected(已失败)。
    • 创建Promise对象时,通过Promise构造函数传递一个执行器函数,该函数接收两个参数resolve和reject,分别用于操作成功和失败时的处理。
  4. Promise的基本用法:

    • 使用.then()处理Promise对象的结果,形成链式调用,每个.then()处理一个异步操作的结果。
    • 使用.catch()捕获Promise链中的任何错误。
    • 使用Promise.all()处理多个Promise对象,等待所有Promise完成。
  5. 异步编程的挑战:

    • 异步编程带来了一些挑战,如回调地狱、并发控制、错误处理等。
    • 这些挑战可以通过Promise和async/await等现代异步编程工具来更优雅地解决。
  6. Promise的状态转换:

    • Promise对象的状态可以从Pending转换为Fulfilled或Rejected。
    • 一旦状态被转换,就不可再次修改,保证了Promise的不可变性。
  7. 示例:

    • 提供了一个以吃饭过程为例的代码,展示了如何使用Promise来处理异步操作,形成Promise链,实现清晰的异步代码流程。

总体而言,这些内容涵盖了JavaScript中异步编程的基本概念、Promise的基本用法和一些异步编程的挑战以及解决方案,在下一篇我们将会聊到Promise的错误处理、Promise的实际应用还有Promise的高级用法等等内容。

结语

那么我们今天的内容就结束啦,欢迎各路大神在评论区讨论~~

点赞收藏不迷路,咱们下期再见ヾ( ̄▽ ̄)ByeBye~