首页 > 解决方案 > JavaScript Promise 如何执行它们的代码?

问题描述

这是我从https://scotch.io/tutorials/javascript-promises-for-dummies窃取并稍作修改的一些代码。

var momIsHappy = false;

var getAPhone = function (whatToDoIfPhoneIsPurchased,
                          whatToDoIfPhoneIsNotPurchased) {
  fulfillPromiseToPurchasePhone = whatToDoIfPhoneIsPurchased;
  breakPromiseToPurchasePhone = whatToDoIfPhoneIsNotPurchased;

  if (momIsHappy) {
      var phone = {
          brand: 'Samsung',
          color: 'black'
      };
      fulfillPromiseToPurchasePhone(phone); // fulfilled
  } else {
      var reason = new Error('mom is not happy');
      breakPromiseToPurchasePhone(reason); // reject
  }
}

var aPhoneIMightGet = new Promise(getAPhone);

var playWithNewPhone = function () {
    findOutIfIGetPhone = aPhoneIMightGet;

    findOutIfIGetPhone
    .then(function (aPhone) {
        // yay, you got a new phone
        console.log(aPhone);
     // output: { brand: 'Samsung', color: 'black' }
    })
    .catch(function (error) {
        // oops, mom don't buy it
        console.log(error.message);
     // output: 'mom is not happy'
    });
};

我不明白的是:什么时候getAPhone调用?我没叫。谁叫的?什么时候?如何?

标签: javascriptcallbackpromise

解决方案


让我们回过头来描述一下 Promise 构造函数的作用。向 Promise 构造函数传递一个同步调用的回调。这个回调(通常称为“Promise 执行器回调”)背后的想法是它会启动一些异步操作,然后立即返回。一段时间后,当该异步操作完成时,它将解决或拒绝该承诺。

这是一个我们可以逐步完成的简单示例(您可以在下面的代码段中运行此示例以查看日志记录序列):

console.log("0");
let p = new Promise(function(resolve, reject) {
    console.log("1");
    setTimeout(function() {
        console.log("2");
        resolve("done");
    }, 1000);
}).then(function(val) {
    console.log("3");
});
console.log("4");

你会得到这个输出:

0     // start of the code
1     // Promise executor callback called and setTimeout() initiated
4     // Promise created and now initialized
2     // setTimeout fires
3     // Promise gets resolved and .then() handler called

而且,以下是发生的事件顺序:

  1. console.log("0");运行
  2. 你调用new Promise()并传递一个回调函数
  3. Promise 构造函数运行,创建一个 Promise 对象并同步调用回调函数
  4. 回调函数的目的是启动一些非阻塞的异步操作(在本例中是一个简单的setTimeout()),但它可能是一个更复杂的操作,例如执行一些文件操作、搜索数据库等......
  5. 回调函数传递了两个参数,每个参数都是一个函数。当回调中启动的异步操作完成时,回调应该调用这两个函数之一。如果这是典型的异步操作,回调将启动异步操作然后返回。然后某个时候,将调用与异步操作相关的不同回调,并且根据操作是成功还是返回错误,该回调将调用传递给回调的两个函数之一(我命名resolvereject在我的示例。您的示例选择了更长的名称。
  6. 在我的代码示例中,Promise 回调(称为 Promise 执行程序)立即登录到控制台,然后调用setTimeout(). 因为setTimeout()是非阻塞和异步的,所以它启动定时器并立即返回。至此,Promise 执行器回调完成并返回。
  7. 计时器现在正在运行,promise executor 回调已运行并已返回。Promise 构造函数完成并返回新的 Promise 对象。
  8. 然后,.then()调用 promise 上的方法。这会为 promise 注册一个完成回调,然后立即返回。从技术上讲,它返回第二个承诺,该承诺链接到使用 Promise 构造函数创建的第一个承诺。这个.then()注册的回调被存储起来,但还没有被调用。
  9. 现在此示例中的最后一行代码执行并记录4到控制台。
  10. 此代码已完成(暂时)。紧随其后的任何其他代码现在都将执行,或者如果没有其他代码,则 Javascript 解释器将返回事件循环并等待下一个事件发生。
  11. 一段时间后,计时器触发。它在 Javascript 事件队列中插入一个事件,当解释器完成它正在做的任何其他事情并从事件队列中获取下一个事件时,它将调用传递给setTimeout().
  12. 当该setTimeout()回调执行时,它将记录2然后调用resolve("done"). 调用resolve()解决第一个承诺并告诉它然后触发任何.then()处理程序并调用它们。
  13. 此时,调用传递给的回调.then()并记录3并完成所有操作。

现在回答有关您的代码示例的一些具体问题:

我不明白的是:什么时候调用 getAPhone?我没叫。谁叫的?什么时候?如何?

您传递getAPhone给 Promise 构造函数。Promise 构造函数调用它作为运行构造函数的一部分以创建新的 Promise 对象。

这是一个如何发生的简单示例:

function doIt(someCallback) {
    someCallback("hi");
}

function myCallback(greeting) {
   console.log(greeting);
}

doIt(myCallback);    // logs "greeting"

当你调用 doIt 时,你传递给它一个函数引用(本质上是一个指向函数的指针)。这允许您调用的函数在它想要调用它时使用它想要的任何参数调用该函数。在这种情况下,doIt()希望您向它传递一个函数引用,它会立即调用该函数并传递它"hi"

那么为什么 promise 构造函数在运行我的函数时不阻塞我的代码呢?如果构造函数所做的只是立即为我调用一个函数,为什么它与直接调用函数不同?

Promise 构造函数是同步运行的(当您调用 时new Promise())。Promise 执行器回调函数由 Promise 构造函数同步运行。但是,它通常所做的只是启动一个异步和非阻塞操作,然后立即返回。然后异步操作在后台运行,并在未来某个时间完成时触发它自己的回调。

那么为什么 Promise 的构造函数中的代码没有阻塞呢?它不是像其他所有东西一样在调用堆栈上的一个函数吗?

Promise 构造函数正在阻塞。它在完成之前不会返回。它是调用堆栈上的一个函数,就像其他所有函数一样。

在这里您可能会感到困惑的是,您的getAPhone()示例不包含任何异步代码。它是完全同步的。根本没有理由在该代码中使用 Promise。

这是该函数的一个不那么钝的版本:

var momIsHappy = false;

var getAPhone = function (resolve, reject) {
  if (momIsHappy) {
      var phone = {
          brand: 'Samsung',
          color: 'black'
      };
      resolve(phone); // fulfilled
  } else {
      var reason = new Error('mom is not happy');
      reject(reason); // reject
  }
}

所有这一切都是检查momIsHappy然后立即解决或拒绝承诺。这里没有异步操作。 根本没有理由在您的示例中使用承诺。 Promise 是管理和协调异步操作的工具。如果您没有任何异步操作,则不应使用它们,因为当可以使用简单和简单的函数调用时,它们会增加同步代码的复杂性。使用异步操作,可能很难协调和管理异步操作和错误处理,特别是当有多个异步操作需要排序或协调并处理所有错误时。这就是 Promise 的设计目的。

因此,在您的特定情况下,您可能只是制作getAPhone()了一个常规函数,直接调用它,根本不使用 Promise。


推荐阅读