2

Promise:解决JavaScript异步操作

Promise 的出现主要是为了解决 JavaScript 中异步操作的处理问题。在传统的 JavaScript 中,异步操作通常使用回调函数来处理,但这种方式会导致代码嵌套层级过深,可读性差,而且容易产生回调地狱(callback hell)的情况,降低了代码的可维护性和可理解性。

简单的说法:它就像是一种特殊的承诺,用来解决 JavaScript 中的异步问题, 想象一下你承诺要跟你朋友五一去北京玩,但是时间还没到。在等待这个过程完成之前,你可以做其他事情,一旦约定的时间到了,你就要履行承诺,如果你去了就履行,但是你也有权利不履行。

Promise解决什么问题

回调地狱:如果有多个异步操作依赖于前一个操作的结果,代码就会出现多层嵌套的回调,造成代码结构混乱难以维护。

 asyncOperation1(callback: () => void) {
    setTimeout(function () {
      console.log('异步操作1完成');
      callback();
    }, 1000);
  }

  asyncOperation2(callback: () => void) {
    setTimeout(function () {
      console.log('异步操作2完成');
      callback();
    }, 1500);
  }

  asyncOperation3(callback: () => void) {
    setTimeout(function () {
      console.log('异步操作3完成');
      callback();
    }, 2000);
  }

  ngOnInit(): void {
    console.log('开始');
    this.asyncOperation1(() => {
      this.asyncOperation2(() => {
        this.asyncOperation3(() => {
          console.log('所有异步操作完成');
        });
      });
    });
}
开始 异步操作1完成 异步操作2完成 异步操作3完成 所有异步操作完成

错误处理困难:错误处理通常依赖于回调函数的调用方式,容易出现漏处理或混乱的情况。

  asyncOperation(callback: (error: any, result?: number) => void) {
    setTimeout( () => {
      const randomNumber = Math.random();
      if (randomNumber < 0.5) {
        callback(null, randomNumber);
      } else {
        callback(new Error('异步操作失败'));
      }
    }, 1000);
  }

  this.asyncOperation((error: any, result?: number) => {
      if (error) {
        console.error('异步操作失败:', error.message);
        // 漏处理错误,没有提供错误处理逻辑
      } else {
        console.log('异步操作成功,结果为:', result);
        // 只处理了成功情况,没有考虑错误处理的可能性
      }
    });
 小于0.5 异步操作失败: 异步操作失败
 大于0.5 异步操作成功: 0.52312344 (随机)

Promise基本使用

Promise 对象通过自身的状态,来控制异步操作。Promise 实例具有三种状态。

  • 待定(pending):初始状态,既没有被兑现,也没有被拒绝。
  • 已兑现(fulfilled):意味着操作成功完成。
  • 已拒绝(rejected):意味着操作失败。

上面三种状态里面,fulfilled和rejected合在一起称为resolved(已定型)。

状态的变化只有2种

  • 从“未完成”到“成功”
  • 从“未完成”到“失败”

状态一旦发生变化,就不会再发生变化,因此,Promise只有2种结果

  • 异步操作成功:从pending到fulfilled
  • 异步操作失败:从pending到rejected

Promise 构造函数

Promise 构造函数是用来创建 Promise 实例的基础。它接受一个带有两个参数的函数作为参数。这个函数会立即执行,通常包含异步操作。这两个参数一般被称为 resolve 和 reject,它们是 JavaScript 引擎提供的回调函数。resolve 函数用于表示异步操作成功并返回结果,而 reject 函数用于表示异步操作失败并返回一个错误对象。

const promise = new Promise((resolve, reject) => {
  const randomNumber = Math.random();

  if (randomNumber < 0.5){
    resolve(randomNumber);
  } else { /* 异步操作失败 */
    reject(new Error(异步操作失败));
  }
});

Promise 的链式调用

.then() 方法最多接受两个参数;第一个参数是 Promise 兑现时的回调函数,第二个参数是 Promise 拒绝时的回调函数。每个 .then() 返回一个新生成的 Promise 对象,这个对象可被用于链式调用,

 const promise = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve("yunzhi");
      }, 300);
    });

    promise
      .then((value) => {
        console.log(value, '异步操作')
      }, (value) => {
        console.log(value, '操作失败')
      });
     // yunzhi 操作成功

   promise
        .then((value) => {
          console.log(value, '操作成功');
          return value + '.club';
        }, (value) => {
          console.log(value, '操作失败')
        }).then(value => {
        console.log(value, '操作成功');
      });

  // yunzhi 操作成功
  // yunzhi.club 操作成功

解决回调地狱

Promise 的链式调用可以使代码更加扁平化、易于理解,并且避免了多层嵌套的回调函数,从而解决了回调地狱问题。

asyncOperation1(callback: () => void) {
    setTimeout(function () {
      console.log('异步操作1完成');
      callback();
    }, 1000);
  }

  asyncOperation2(callback: () => void) {
    setTimeout(function () {
      console.log('异步操作2完成');
      callback();
    }, 1500);
  }

  asyncOperation3(callback: () => void) {
    setTimeout(function () {
      console.log('异步操作3完成');
      callback();
    }, 2000);
  }

asyncOperation1()
    .then(() => {
        return asyncOperation2();
    })
    .then(() => {
        return asyncOperation3();
    })
    .then(() => {
        console.log('所有异步操作完成');
    });
开始 异步操作1完成 异步操作2完成 异步操作3完成 所有异步操作完成

Promise 的链式调用和 .catch() 方法来更可靠地处理错误,漏处理或混乱的情况

 this.asyncOperation().then((result) => {
      console.log('异步操作成功,结果为:', result);
    }).catch((error) => {
      console.error('异步操作失败:', error.message); 
     // 在 .catch() 中处理异步操作失败的情况
    })

asyncOperation() {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
          const randomNumber = Math.random();
          if (randomNumber < 0.5) {
            resolve(randomNumber);
          }
          reject(new Error('异步操作1失败'));
        }
        , 1000)
    })
  }
 小于0.5 异步操作失败: 异步操作失败
 大于0.5 异步操作成功: 0.52312344 (随机)

总结

通过 Promise 构造函数,我们可以更加灵活地处理异步操作,并且避免了传统回调函数所带来的回调地狱问题。更好的错误处理: Promise 提供了统一的错误处理机制,使得错误更容易捕获和处理。通过 .catch() 方法,可以捕获到 Promise 链中的任何一个 Promise 对象的错误。

同步和异步的区别

同步操作

同步操作是按照代码的顺序执行的。当你执行一个同步操作时,JavaScript 引擎会阻塞代码的执行,直到这个操作完成,然后才会继续往下执行。

console.log("开始");
console.log("yunzhi");
console.log("结束");
// 开始
// yunzhi
// 结束

异步操作:

异步操作允许代码在执行某些操作时继续往下执行,而不需要等待这些操作完成。当操作完成后,通常会执行一个回调函数来处理结果。

JavaScript 中常见的异步操作包括定时器函数(如 setTimeout、setInterval)、事件监听(如点击事件、网络请求完成事件)、Promise 等。

console.log("开始");

setTimeout(() => {
    console.log("yunzhi");
}, 2000); // 2秒后执行

console.log("结束");

// 开始
// 结束
// yunzhi

异步操作的原理:

JavaScript 是单线程执行的,意味着一次只能执行一个任务。当遇到异步操作时,JavaScript 将这些操作交给浏览器的其他线程(比如定时器线程、网络请求线程等)来处理,自己则继续执行后续的代码。当异步操作完成后,会将对应的回调函数加入任务队列(task queue)中。一旦主线程空闲,事件循环(event loop)就会检查任务队列,如果有任务,就将其取出并执行。

image.png

GUI 渲染线程:
主要负责页面的渲染,解析 HTML,CSS,构建 DOM 树,布局和绘制等。

JS 引擎线程:
这个线程就是负责执行JS的主线程主要负责处理 JavaScript 脚本,"JS是单线程的"就是指的这个线程, 也主要负责执行准备好的待执行的事件,即定时器结束,或者异步请求成功并正确返回时,将依次进入任务 队列,等待 JS 引擎线程的执行。

定时触发线程:
前面异步例子的setTimeout其实就运行在这里,他跟JS主线程根本不在同一个地方,所以“单线程的JS”能够实现异步。JS的定时器方法还有setInterval,也是在这个线程。

事件触发线程:
事件触发线程是指浏览器中的一个特殊线程,它负责监听和处理用户的交互事件,例如点击、滚动、键等。当这些事件发生时,事件触发线程会将事件加入到任务队列中,并通知主线程来处理这些事件。

异步HTTP请求线程:
这个线程负责处理异步的ajax请求,当请求完成后,他也会通知事件触发线程,然后事件触发线程将这个事件放入任务队列给主线程执行。

事件循环:
事件循环(Event Loop)是 JavaScript 运行时环境中一种重要的机制,它负责管理和协调代码的执行顺序,确保异步任务能够按照预期顺利执行,并且保证了 JavaScript 单线程的特性。

image.png

  console.log("开始");

  setTimeout(() => {
      console.log("yunzhi");
  }, 0);

  Promise.resolve()
    .then(() => console.log("结束"));

  console.log("测试");

  // 开始
  // 测试
  // 结束
  // yunzhi

参考文章

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Refer...
https://www.runoob.com/js/js-promise.html

https://juejin.cn/post/7073728727913332744#heading-8


kexb
474 声望15 粉丝

« 上一篇
nginx使用案例