承诺如何保证'解决'函数在异步操作后执行

时间:2017-05-28 01:49:49

标签: javascript promise

我有这段代码:

var myFirstPromise2 = new Promise((resolve, reject) => {
    //setTimeout models an operation that takes a long time
    setTimeout(function(){console.log('finishTimeout')},60000);
    resolve('success');
});

myFirstPromise2.then((successMessage) => {
    console.log('yay '+successMessage);
});

输出结果为:

yay success 
finishTimeout

我希望在冗长的操作完成后执行resolve(),以便反转输出。

我也对一般的承诺感到困惑。上面的代码与简单地执行此操作没有区别:

setTimeout(function(){console.log('finishTimeout')},60000);
console.log('yay success');

编辑:resolve()置于setTimeout之内,区别于此:

setTimeout(function() {
    console.log('finishTimeout')
    console.log("yay success")
},60000)

3 个答案:

答案 0 :(得分:5)

  

承诺如何保证'解决'函数在异步操作后执行

他们没有。 Promise只是一个通知和错误传播系统。它们仅在某些代码调用resolve()来解析承诺时解析承诺。在异步操作实际完成之前,您不应该调用resolve()。如果您过早致电resolve()(就像您正在做的那样),承诺将在异步操作完成之前过早解决。

此处的代码在resolve()回调实际触发之前调用setTimeout(),因此承诺很快得到解决:

var myFirstPromise2 = new Promise((resolve, reject) => {

  //setTimeout models an operation that takes a long time
  setTimeout(function(){console.log('finishTimeout')},60000);
  resolve('success');
});

该代码应该是在setTimeout()回调中调用resolve的代码,因此在计时器触发之后才会解析promise:

var myFirstPromise2 = new Promise((resolve, reject) => {

  //setTimeout models an operation that takes a long time
  setTimeout(function(){
      console.log('finishTimeout');
      resolve('success');
  },60000);
});

注意:仅在实际执行异步操作时调用resolve()。您的版本在异步操作完成之前调用它,因此在您的版本中,承诺在异步操作完成之前很快得到解决。

Promise没有任何神奇的力量可以知道异步操作何时完成。它们只完成您的代码告诉他们要做的事情。因此,只有在异步操作实际完成时才调用resolve()

  

编辑:将setTimeout中的解决方案放在这个区别之间有什么区别:

setTimeout(function() {
    console.log('finishTimeout')
    console.log("yay success")
},60000);

在这个具体的例子中,这将正常工作。 Promise用作异步操作的组织和管理工具。当您有多个需要排序或协调的异步操作时,它们非常有用,在编写涉及多个异步操作的强大错误处理时它们非常有用。你并不需要承诺进行简单的单一计时器操作。

但是,一旦你开始使用promises用于高价值用途,你会发现它们只是设计和编码所有异步操作的更好方法,即使是更简单的操作。您会发现几乎每次异步操作都要使用它们。您没有必要,但我的经验是,一旦您开始使用它们,将它们用于所有异步操作会更容易,更简单。

注意:从技术上讲,这之间存在细微差别:

setTimeout(function() {
    console.log('finishTimeout')
    console.log("yay success")
},60000);

而且,这个:

var myFirstPromise2 = new Promise((resolve, reject) => {

  //setTimeout models an operation that takes a long time
  setTimeout(function(){
      console.log('finishTimeout');
      resolve('success');
  },60000);
});

myFirstPromise2.then((successMessage) => {
    console.log('yay '+successMessage);
});

因为所有.then()处理程序在下一个时钟执行时执行,所以第二个代码示例中的两个console.log()操作与第一个操作之间的延迟会稍长(以毫秒为单位)延迟。这可能不重要,但既然你问的不同之处,我想我会指出那个微小的差异。有一些实际的原因可以解释为什么设计这种方式总体上是一个很好的设计决策(在调用.then()处理程序之前堆栈是解开的,解析一致是异步的,即使同步解析了promise等等)。

答案 1 :(得分:1)

绝对没有什么神奇之处。 如前所述,您必须使用您希望传递的值手动调用resolve(或reject),并且必须确保在代码解析时执行此操作。这意味着,如果它是异步任务,那么您正在解析该任务的回调;不是之前。

所有这一切都是提供先前定义的函数,并将向下游触发值。

function Promise (task) {
  let onSuccess;
  let onError;
  let resolve = value => onSuccess(value);
  let reject = err => onError(err);

  task(resolve, reject);

  return {
    then: (handleSuccess, handleError) => {
      return Promise(function (resolve, reject) {
        onSuccess = result => {
          const value = handleSuccess(result);
          if (value && value.then) {
            value.then(resolve, reject);
          } else {
            resolve(value);
          }
        };
        onError = error => {
          const value = handleError(error);
          if (value && value.then) {
            value.then(resolve, reject);
          } else {
            resolve(value);
          }
        };
      });
    }
  };
}

请注意,这是Promise的实施不足;这个版本只适用于异步代码,并且缺少很多用例。但是重要的机制就在那里,供你分析。

当你打电话给它并给它处理程序时,它会返回一个新的Promise。该承诺的任务基本上是订阅了父母Promise的成功或失败等等。

正如您所看到的,Promise内部没有神奇的暂停按钮,可以让您知道任务何时完成。它实际上只是设置了内部回调,触发了下一个也完成的承诺。

一旦考虑到所有错误处理,回调排队和状态管理,您就可以在大约100行代码中完整实现ES6 Promise。

Future是一个类似于Promise的结构,但更容易实现(如果您不习惯函数式编程,则更难以包围)。

function Future (task) {
  return {
    map:
      f =>
        Future((reject, resolve) =>
          task(reject, x => resolve(f(x)))),

    chain:
      f =>
        Future((reject, resolve) =>
          task(reject, x => f(x).fork(reject, resolve))),

    fork: task
  };
}

Future.of = x => Future((_, resolve) => resolve(x));
Future.resolved = Future.of;
Future.rejected = err => Future(reject => reject(err));

实际上,您可以查看该示例中map的实现,并查看与onSuccess之前的最终回调相同的调度。说实话,实施Future的方式远不如你所看到的那么多。它更容易的原因仅仅是因为它不会运行异步代码,直到您手动调用fork并传递错误和成功处理程序,并且因为它没有试图找出如果您从承诺中返回承诺,订阅或只是运行回调......

...如果您只想返回一个值,请使用map,如果您想返回值的未来,请使用chain。

const app = Future.of(20)
  .map(x => x * 2)
  .chain(x => Future.of(x / 4));

app.fork(err => console.log(err), num => console.log(num)); // 10

答案 2 :(得分:0)

它没有按预期工作的原因是因为同步返回resolve而不是异步。 Promise的优点在于你可以在异步进程内传递resolve,当进程完成时,执行resolve被称为回调。这就是你能做到的原因:

setTimeout(function() {
  console.log('finishTimeout')
  resolve("success")
},60000)

它应该按预期工作。