kufudo
kufudo

Reputation: 121

new Promise() vs (async () => {})()

Block 1:

const promise = new Promise((resolve) => {
  setTimeout(resolve, 100);
});

Block 2:

const promise = (async () => {
  await new Promise(resolve => {
    setTimeout(resolve, 100);
  });
})();

Are the above two blocks equivalent? Any note-worthy differences?

I know it's a highly contrived example and there's not much purpose for Block 2 here. But a situation I have is where I want to create and store a reference to a promise, but the promise executor function needs to use await to get some data. But declaring new Promise(async (resolve) => {}); is considered an anti-pattern. Is block 2 in this situation any better?

UPDATE: Providing a more concrete example of what I'm trying to do:

  export async function getData(request) {
    // De-dupe fetches for the same request and return existing promise.
    if (map.has(JSON.stringify(request))) {
      return map.get(JSON.stringify(request));
    }

    const thePromise = (async () => {
      const [foo, bar] = Promise.all(await getFoo(), await getBar());

      const theData = await getTheData(foo, bar);

      return theData.some.thing ? 'a' : 'b';
    })();

    map.put(JSON.stringify(request), thePromise);
    return thePromise;
  }

Upvotes: 5

Views: 3337

Answers (3)

cccn
cccn

Reputation: 949

In the second approach you can use try and catch blocks, like so:

const promise = (async () => {
  try {
    await new Promise(resolve => {
      setTimeout(resolve, 100);
    });
  } catch(err) {
    // handle error here
  }
})();

Upvotes: 1

Bergi
Bergi

Reputation: 664307

Providing a more concrete example of what I'm trying to do

Your usage of the async immediately executed function expression is totally fine here, there's nothing wrong with it. Sure, you could have written the same with a .then() chain, but that wouldn't be exactly as comfortable.

Some minor improvements:

export function getData(request) {
//    ^ no need for `async` when you return a promise anyway
  const requestKey = JSON.stringify(request); // compute only once
  if (map.has(requestKey)) {
    return map.get(requestKey);
  }
  const thePromise = (async () => {
    const [foo, bar] = await Promise.all([getFoo(), getBar()]);
//                     ^^^^^ had `await` keywords in the wrong places
    const theData = await getTheData(foo, bar);
    return theData.some.thing ? 'a' : 'b';
  })();
  map.set(requestKey, thePromise);
  return thePromise;
}

Upvotes: 0

goto
goto

Reputation: 4425

Not sure if this is what you're referring to when you say

but the promise executor function needs to use await to get some data

but it sounds like you're dealing with some asynchronous call inside of a promise that you need to "wait" for, then resolve with the data that was returned as a result.

So for example,

EDIT: As pointed by @Bergi in the comments below, you probably shouldn't be doing this (wrapping a promise inside of another promise):

// Assuming you're making an API with `axios` to grab some data
const promise = new Promise((resolve, reject) => {
  axios.get('www.example.com/api', {...})
    .then(result => {
      resolve(result)
    })
    .catch(error => {
      reject(error)
    })
})
const callback = (data) => {
  console.log('data', data)
}

// `callback` will get called when the `axios.get` "finishes" (resolves) 
// with some data after it's done fetching, no need to `await` here

promise.then(callback)

Instead, you could do promise chaining, if needed:

// using the same example from above
const handleResponse = (result) => {
  // handle response
}
const handleError = (error) => {
  // handle error
}

axios.get('www.example.com/api', {...})
  .then(handleResponse)
  .then(doSomethingElse)
  .catch(handleError)

// or, if you need to make multiple asynchronous calls use `Promise.all`
const handleResponses = (response) => {
  const [users, books] = response
  // do the rest
}

Promise.all([
  axios.get('www.example.com/api/users'),
  axios.get('www.example.com/api/books')
])
  .then(handleAPIResponses)
  .then(doSomethingElse)
  .catch(handleError)

Similarly, if you're dealing with the "error first" callback pattern

// `fs.readFile` with Node.js
const promise = new Promise((resolve, reject) => {
  fs.readFile('...', (err, data) => {
    if (err) {
      reject(err)
      return
    }
    resolve(data)
  })
})
const callback = (data) => {
  console.log('data', data)
}

// again, `callback` will get called when reading a file is done
// and you get the result back

promise.then(callback)

If you need to make multiple calls inside of a promise and then resolve with the final value, you could do something like the following:

async function promise() {
  try {
    // assuming API.getUserIds() returns a promise
    const userIds = await API.getUserIds(...)
    const users = await API.getUsers(userIds)

    return users
  } catch (err) {
    // ...
  }
}
const callback = (data) => {
  console.log('data', data)
}

// again, `callback` will get called when both API requests 
// resolve with some values and when the above function returns `users`

promise().then(callback)

Personally, I'd stay away from #2, and whatever you're trying to accomplish with that pattern (depending on your case) can be easily done choosing one of the examples above.

Upvotes: 0

Related Questions