简体   繁体   中英

How do promises guarantee that the 'resolve' function is executed after an asynchronous operation

I have this code:

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);
});

The output is:

yay success 
finishTimeout

I want resolve() to be executed after the lengthy operation is finished, so that the outputs are reversed.

I am also confused about promises in general. The code above is no different from simply doing this:

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

EDIT: What is the difference between putting resolve() inside setTimeout , and just this:

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

How do promises guarantee that the 'resolve' function is executed after an asynchronous operation

They don't. Promises are only a notification and error propagating system. They only resolve a promise when some code calls resolve() to resolve the promise. You shouldn't be calling resolve() until the async operation is actually done. If you call resolve() too soon (like you were doing), the promise will resolve too soon before the async operation is complete.

Your code here is calling resolve() before the setTimeout() callback actually fires and thus the promise is resolved too soon:

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

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

That code should be this where resolve is called inside the setTimeout() callback so the promise is not resolved until after the timer has fired:

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

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

Note: resolve() is only called when the async operation is actually done. Your version was calling it before the async operation was done and thus in your version the promise was resolved too soon before the async operation was done.

Promises do NOT have any magic powers to know when asynchronous operations are done. They only do exactly what your code tells them to do. So, only call resolve() when the asynchronous operation is actually done.


EDIT: what is the difference between putting resolve inside setTimeout from just this:

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

In this specific example, this will work just fine. Promises are used as an organizing and management tool for asynchronous operations. They are extremely useful when you have multiple asynchronous operations that you need to sequence or coordinate and they are extremely useful when writing robust error handling involving multiple asynchronous operations. You don't really need a promise for a simple, single timer operation.

But, once you start using promises for the high value uses, you will find they are just a better way to design and code all your asynchronous operations, even the simpler ones. You will find you want to use them for pretty much every asynchronous operation. You don't have to, but my experience is once you start using them, it's easier and simpler to use them for all async operations.

Note: Technically, there is a slight difference between this:

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

And, this:

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);
});

Because all .then() handlers are executed on the next tick, there will be a slightly longer (measured in ms) delay between the two console.log() operations in the second code example versus the first one. It's probably not material, but since you asked what the difference is, I thought I'd point out that slight difference. There are practical reasons why it is designed this way which are overall a good design decision (stacks are unwound before .then() handlers are called, resolves are consistently asynchronous, even if the promise is resolved synchronously, etc...).

There is absolutely nothing magical. As has been said, you have to manually call resolve (or reject ) with the value that you wish to pass on, and you have to make sure you do so at the time your code has resolved. That means that if it's an async task, that you are resolving in the callback of that task; not before.

All this is doing is providing functions which were defined earlier, and will fire values downstream.

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);
          }
        };
      });
    }
  };
}

Note that this is very much an insufficient implementation of a Promise; this version would only ever work for async code, and is missing a lot of use cases. But the important mechanics are there, for you to analyze.

When you call then, and give it handlers, it returns a new Promise. That promise's task is basically subscribing to the parent Promise's success or failure, et cetera.

As you can see, though, there is no magical pause button inside of a Promise that makes it able to know when a task is finished. It's really just setting up internal callbacks that trigger the next promise to also complete.

You can make a full implementation of ES6 Promises in ~100 lines of code, once you account for all error-handling, and callback queuing and state-management.

Future is a structure like a Promise but is easier to implement (though harder to wrap your head around, if you aren't used to functional programming).

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));

Really, you can look at the implementation of map in that example, and see the same sort of scheduling of eventual callback as onSuccess previously. To be honest, there isn't much more in the way of implementing Future than what you see. The reason it's easier is simply because it doesn't run the async code until you manually call fork and pass it error and success handlers, and because it doesn't try to figure out if you returned a promise from a promise, to subscribe or just run a callback...

...if you just want to return a value, use map, if you want to return a future of a value, then use 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

The reason why it is not working as expected is because resolve is being returned synchronously instead of asynchronously. That's the great thing about promises is that you can pass resolve inside of an asynchronous process and when the process is done executing resolve is called as a callback. That is why you can do:

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

And it should work as expected.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM