【JavaScript底层】回调、Promise、async/await傻傻分不清楚?——JS异步特性汇总

反教条主义,通俗和理解战胜一切。概念是为实用服务的。现有想法后有概念,切莫本末倒置!

JS异步

注: 只谈出现的原因、区别与总结。细节此处不予赘述 。理解这些异步方法之前一定要理解事件循环机制,如果不了解请看相应文章先把事件循环搞明白噢~

原始方法——回调

由于JS基于事件循环,经常存在异步的概念。
JavaScript 主机(host)环境提供了许多函数,这些函数允许我们计划 异步 行为。换句话说,我们现在开始执行的行为,但它们会在稍后完成。

例如,setTimeout 函数就是一个这样的函数。

回调地狱/厄运金字塔

对于一个接一个的多个异步行为,代码将会变成这样:

loadScript('1.js', function(error, script) {

  if (error) {
    handleError(error);
  } else {
    // ...
    loadScript('2.js', function(error, script) {
      if (error) {
        handleError(error);
      } else {
        // ...
        loadScript('3.js', function(error, script) {
          if (error) {
            handleError(error);
          } else {
            // ...加载完所有脚本后继续 (*)
          }
        });

      }
    });
  }
});

为战胜回调地狱而诞生——Promise

promise(承诺)顾名思义…
本质上其实是生产者消费者模型或订阅模型
这种类比并不十分准确,因为 JavaScipt 的 promise 比简单的订阅列表更加复杂:它们还拥有其他的功能和局限性。但以此开始挺好的。
Promise构造器

let promise = new Promise(function(resolve, reject) {
  // executor(生产者代码,“歌手”)
});

以setTimeout为例

let promise = new Promise(function(resolve, reject) {
  // 当 promise 被构造完成时,自动执行此函数

  // 1 秒后发出工作已经被完成的信号,并带有结果 "done"
  setTimeout(() => resolve("done"), 1000);
});
let promise = new Promise(function(resolve, reject) {
  // 1 秒后发出工作已经被完成的信号,并带有 error
  setTimeout(() => reject(new Error("Whoops!")), 1000);
});

Promise 则更加灵活。我们可以随时添加处理程序(handler):如果结果已经在了,它们就会执行。

以setTimeout为例

let promise = new Promise(function(resolve, reject) {
  // 当 promise 被构造完成时,自动执行此函数

  // 1 秒后发出工作已经被完成的信号,并带有结果 "done"
  setTimeout(() => resolve("done"), 1000);
});

.then接受“thenables”

确切地说,处理程序(handler)返回的不完全是一个 promise,而是返回的被称为 “thenable” 对象 — 一个具有方法 .then 的任意对象。它会被当做一个 promise 来对待。

这个想法是,第三方库可以实现自己的“promise 兼容(promise-compatible)”对象。它们可以具有扩展的方法集,但也与原生的 promise 兼容,因为它们实现了 .then 方法。

这是一个 thenable 对象的示例:

class Thenable {
  constructor(num) {
    this.num = num;
  }
  then(resolve, reject) {
    alert(resolve); // function() { native code }
    // 1 秒后使用 this.num*2 进行 resolve
    setTimeout(() => resolve(this.num * 2), 1000); // (**)
  }
}

new Promise(resolve => resolve(1))
  .then(result => {
    return new Thenable(result); // (*)
  })
  .then(alert); // 1000ms 后显示 2

JavaScript 检查在 (*) 行中由 .then 处理程序(handler)返回的对象:如果它具有名为 then 的可调用方法,那么它将调用该方法并提供原生的函数 resolve 和 reject 作为参数(类似于 executor),并等待直到其中一个函数被调用。在上面的示例中,resolve(2) 在 1 秒后被调用 (**)。然后,result 会被进一步沿着链向下传递。

这个特性允许我们将自定义的对象与 promise 链集成在一起,而不必继承自 Promise。

Async/await

Async/await 本质上是以更舒适的方式使用 promise 的一种特殊语法,同时它也非常易于理解和使用。
本质上返回的还是Promise

以setTimeOut为例子

async function f() {

  let promise = new Promise((resolve, reject) => {
    setTimeout(() => resolve("done!"), 1000)
  });

  let result = await promise; // 等待,直到 promise resolve (*)

  alert(result); // "done!"
}

f();

以上代码用Promise包裹setTimeout返回Promise对象

await 接受 “thenables”

像 promise.then 那样,await 允许我们使用 thenable 对象(那些具有可调用的 then 方法的对象)。这里的想法是,第三方对象可能不是一个 promise,但却是 promise 兼容的:如果这些对象支持 .then,那么就可以对它们使用 await。
这有一个用于演示的 Thenable 类,下面的 await 接受了该类的实例:

class Thenable {
  constructor(num) {
    this.num = num;
  }
  then(resolve, reject) {
    alert(resolve);
    // 1000ms 后使用 this.num*2 进行 resolve
    setTimeout(() => resolve(this.num * 2), 1000); // (*)
  }
}

async function f() {
  // 等待 1 秒,之后 result 变为 2
  let result = await new Thenable(1);
  alert(result);
}

f();

如果 await 接收了一个非 promise 的但是提供了 .then 方法的对象,它就会调用这个 .then 方法,并将内建的函数 resolve 和 reject 作为参数传入(就像它对待一个常规的 Promise executor 时一样)。然后 await 等待直到这两个函数中的某个被调用(在上面这个例子中发生在 (*) 行),然后使用得到的结果继续执行后续任务。

用例对比

示例:loadScript

function loadScript(src, callback) {
  let script = document.createElement('script');
  script.src = src;

  script.onload = () => callback(null, script);
  script.onerror = () => callback(new Error(`Script load error for ${src}`));

  document.head.append(script);
}

Resolve/reject 可以立即进行
关于这个知识点补充一点
🌰

let promise = new Promise((resolve, reject) => {
console.log(1)
resolve()
})
promise.then(() => console.log(3))

console.log(2)

上面代码在控制台打印的结果是 1, 2, 3

因为: 在 new Promise()的时候,Promise的执行器就会立马执行,但是调用resolve()会触发异步操作,传入的then()方法的函数会被添加到任务队列并异步执行

参考资料

《现代 JavaScript 教程》

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值