【问题标题】:how .then() method actually works in JavaScript?.then() 方法如何在 JavaScript 中实际工作?
【发布时间】:2021-04-04 03:43:49
【问题描述】:

我对 Promise 有疑问,这让我很困惑。

.then() 方法

在我进入关于 .then() 方法让我感到困惑的事情之前,我将根据我的知识对 Javascript 引擎的工作原理做一个简短的解释。

据我所知,Javascript 不是异步语言,而是同步语言

Javascript 引擎同步工作,但 Javascript 引擎并不是浏览器上唯一运行的东西

渲染引擎、setTimeout、HTTP 请求等

Javascript 引擎可以在 我们调用原生函数时与它们对话 setTimeout 所以setTimeout 函数将调用Javascript引擎之外的程序进行计时

当然,当计时器结束时,它会将回调发送到事件队列,并且只有在 Javascript 完成所有 Executions 上下文之后,它才会查看事件队列

好的,现在让我们转到 Promise 我的问题是.then() 如何知道何时调用resolve() 我读过文章,他们说.then() 异步工作,这对我来说听起来很奇怪,因为 Javascript 是同步的,不是吗? 也许我没有正确理解它们

所以我对.then() 的工作原理做出了自己的假设,因为我还没有找到让我感觉和自信我确切知道.then() 工作原理的来源。

其中一个(我的假设)是.then 方法中有两个阶段

我将使用此代码进行演示来解释我的假设

var myPromise = new Promise(function(resolve){
                    resolve('Hello Stackoverflow !');
                 });
                 
                 myPromise.then(function(result){
                    console.log(result);
                 });

所以根据我的假设 resolve('Hello Stackoverflow !') 函数调用 .then 方法和 .then这里检查两件事如下

1..then()的回调参数是否被创建

2.如果 Promise 状态设置为已解决

如果两个条件都为真,那么.then() 方法会将带有值Hello Stackoverflow ! 的回调插入到事件队列中,并且只有在堆栈中弹出所有执行上下文之后,它才会运行回调,我们将得到结果Hello Stackoverflow !

这又是仅基于我的假设也许我完全错了。

因此,如果您检查我刚才所说的内容,您可以得出这样的结论:.then 方法被调用了两次 为什么?

第一次是当解析函数调用它但并非所有条件都为真时 不正确的是 .then 回调参数已创建但尚未创建 因为我们还没有到我们创建回调的代码行,所以条件为假

第二次是当我们调用.then 方法并创建回调并且所有条件现在都为真时,它会将回调插入事件队列,并且在所有执行上下文将弹出堆栈然后回调将被调用 我们会得到Hello Stackoverflow !

希望你能理解我试图解释的内容

我是对还是错?

提前感谢大家:)

【问题讨论】:

  • .then() 方法在myPromise 初始化后立即被调用。 .then() 方法和传递给 .then()callback 是两个完全不同的东西。
  • 使用 Promises 安排的回调不会添加到任务队列中 - 它们是 added to the micro-task queue
  • "如果两个条件都为真,[它] 会将带有值的回调插入到事件队列中" - 是的。但是如果 promise 还没有实现,then 方法只是将回调存储在一个内部列表中,以便通过resolve 调用将其放入事件队列中。
  • 我可以建议阅读相同主题的答案。
  • 由于一些奇怪的原因,我们似乎错过了现在成为 Js 引擎一部分的 WEB API 的概念,这是一篇非常好的文章,因为通过 web API 承诺在幕后发生的事情是 web 线程池的一部分使异步操作的 API dev.to/steelvoltage/…

标签: javascript promise


【解决方案1】:

可以得出.then方法被调用两次的结论

这不是真的。 then 方法的调用就像调用任何方法一样:当具有该方法调用的语句/表达式在正常执行流程中被求值时。

你的例子的同步执行顺序如下:

  1. 回调函数function(resolve) {...}被传递给Promise构造函数。

  2. 此构造函数立即执行它作为参数接收的回调,并传递一个resolvereject 参数。

  3. resolve 被调用,并将字符串传递给它。

  4. 实现了resolve 函数的promise 实现因此被通知并将promise 对象的状态设置为已完成,并注册它的已完成值(字符串)。它还将作业放入 Promise Job 队列。

  5. promise构造函数执行完毕,返回promise实例,赋值给var myPromise

  6. 回调函数function(result) {...}被传递给myPromise.then()方法。

  7. 本机then 实现注册回调,但不执行。

  8. 本机then 函数完成执行并返回一个新的promise。你的脚本没有捕捉到这个返回值,所以让我们把这个新的承诺标记为promiseB

  9. 脚本结束,调用堆栈为空。

现在我们进入通常所说的异步执行部分,它总是以作业/事件队列中的条目开始:

  1. 主机将检查哪些作业队列有条目,优先考虑具有高优先级的作业队列。 Promise 作业队列具有非常高的优先级,通常高于处理用户交互或其他外部事件的事件队列。因此,在上述第 4 步中放入队列的作业将被从 Promise 作业队列中取出。

  2. 上述作业执行。

  3. 此作业将(一个接一个)调用myPromise 对象上已注册为then 回调的回调函数(例如在步骤7 中注册的回调函数)。注意:为了简单起见,我在这里忽略了async/await 语法。

  4. 所以在这种情况下,脚本中唯一的then-callback 会被执行——不要与then 方法本身(它已经在第6 步中执行)混淆了。执行 then-callback 时使用的参数是承诺 myPromise 的值(在步骤 4 中注册的字符串)。

  5. console.log 被执行。

  6. then 回调完成执行并返回undefined

  7. promiseB 用这个返回值实现(在这种情况下为undefined)。

  8. 作业执行完成。调用堆栈又是空的。

【讨论】:

  • 终于,明白了,你能用第二个例子改进你的答案吗?使用 async/await 语法?
  • @shellwhale,就像我在回答中写的那样,我不想去那里,所以专注于这里提出的问题。如果您对async/await 有疑问并且在此站点上找不到答案,请随时发布,我很乐意查看。
【解决方案2】:

一个非常基本的 Promise 准系统实现,它可能会回答你的问题:

class Promise {
  constructor(fn) {
    this.status = "PENDING";
    this.result = null;
    this.successCB = [];
    this.errorCB = [];
    fn(this.resolve, this.reject); // This actually goes into microtask
  }

  resolve = data => {
    this.status = "SUCCESS";
    this.result = data;
    this.successCB.forEach(eachCB => eachCB(data));
    this.successCB = [];
  };

  reject = error => {
    this.status = "FAILED";
    this.result = error;
    this.errorCB.forEach(eachCB => eachCB(error));
    this.errorCB = [];
  };

  then = (successCB, errorCB) => {
    switch (this.status) {
      case "PENDING":
        this.successCB.push(successCB);
        this.errorCB.push(errorCB);
        break;
      case "SUCCESS":
        successCB(this.result);
        break;
      case "FAILED":
        errorCB(this.result);
        break;
      default:
        break;
    }
  };
}

为了简单起见,我没有考虑链接承诺或高级错误处理。但这应该在解析完​​成之前/之后执行 then 时起作用。

【讨论】:

  • "// This actually goes into microtask" 注释在错误的行上。它应该在forEachs 和已解决的cases 中的呼叫上。
  • 该函数在 Promise 创建期间执行,与 then 无关。因此,它被委托给构造函数中的微任务。
  • 不,它不是微任务,执行器回调是从构造函数中同步调用的。微任务只涉及传递给then 的处理程序。
  • 我不知道。谢谢!
  • 这会同步调用回调。其次,它允许承诺状态从已完成切换到已拒绝,反之亦然。这不是它应该如何工作的。
猜你喜欢
  • 1970-01-01
  • 2012-12-08
  • 2023-03-25
  • 2017-03-07
  • 2015-05-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-02-21
相关资源
最近更新 更多