Code Monkey home page Code Monkey logo

Comments (8)

glasser avatar glasser commented on April 28, 2024 1

Just FYI, your package-lock.json seems to have been created with some internal corporate npm mirror so that the URLs it tries to install are all from a private mirror. I'll delete the package-lock.json file when trying to reproduce but that might mean I get a different version of packages!

from apollo-server.

glasser avatar glasser commented on April 28, 2024

I think this is basically working as intended, and it's not actually related to Apollo Server or GraphQL at all, just how Promises/async-await work in Node.JS.

When you write this code:

const myResolverC = async () => {
  const x = thisPromiseWillReject();
  await thisPromiseWillResolveAfterALongTime();
  await x;
}

there is no error handling connected to the x promise until the await x happens. As far as the JS runtime is concerned, nothing in the world is paying attention to the rejection of this Promise (yet!), and so any rejection is considered unhandled. Since Node v15 this will get translated (if you don't set an unhandledRejection handler) into an uncaught exception, which by default crashes your program.

Functions that call multiple Promise-returning functions should use a utility like Promise.all, Promise.allSettled, Promise.race, or Promise.any, all of which ensure that all of the Promises you pass to it get an appropriate error handler attached to it (or use .catch yourself making sure to .catch every Promise before waiting on any of them).

from apollo-server.

glasser avatar glasser commented on April 28, 2024

To demonstrate that this is unrelated to AS or graphql, replace your server.mjs with:

const REJECTION_TIMER_MILLIS = 1000;
const RESOLUTION_TIMER_MILLIS = 3000;

const thisPromiseWillReject = () => {
  return new Promise((_, reject) => setTimeout(() => {
    reject(new Error('I am rejecting.'));
  }, REJECTION_TIMER_MILLIS));
}

const thisPromiseWillResolveAfterALongTime = () => {
  return new Promise((resolve) => setTimeout(resolve, RESOLUTION_TIMER_MILLIS));
}

const myResolverC = async () => {
  const x = thisPromiseWillReject();
  await thisPromiseWillResolveAfterALongTime();
  await x;
}


try {
  await myResolverC();
} catch (e) {
  console.log(`Error caught from await: ${e}`)
}

Running this shows:

$ node server.mjs 
file:///Users/glasser/Projects/Apollo/mock-apollo-server-for-issue/server.mjs:6
    reject(new Error('I am rejecting.'));
           ^

Error: I am rejecting.
    at Timeout._onTimeout (file:///Users/glasser/Projects/Apollo/mock-apollo-server-for-issue/server.mjs:6:12)
    at listOnTimeout (node:internal/timers:573:17)
    at process.processTimers (node:internal/timers:514:7)

Node.js v21.6.2

Note that you do not get the "Error caught from await" message here; this is the same issue, demonstrated without any use of @apollo/server or graphql.

from apollo-server.

mfanegg avatar mfanegg commented on April 28, 2024

Just FYI, your package-lock.json seems to have been created with some internal corporate npm mirror so that the URLs it tries to install are all from a private mirror. I'll delete the package-lock.json file when trying to reproduce but that might mean I get a different version of packages!

thanks for the heads up, I replaced the repro with a public one

from apollo-server.

mfanegg avatar mfanegg commented on April 28, 2024

@glasser I totally agree that uncaught exceptions should not exist in the app in the first place, but my concern comes from apollo-server being able to handle some uncaught exceptions, but crashing on others.

As you noted, the example I provided has an uncaught exception:

const myResolverC = async () => {
  const x = thisPromiseWillReject();
  await thisPromiseWillResolveAfterALongTime();
  await x;
}

because myResolverC is not handled until await happens. This turns into an uncaught exception as you noted, which crashes the app.

But this resolver is handled totally fine by apollo server:

const myFailingResolver = async () => {
  throw new Error('yikes');
}

Even though it's the same end result (uncaught exception). Maybe I'm missing something? It would be ideal if all unhandled exceptions were treated the same way by apollo-server.

from apollo-server.

glasser avatar glasser commented on April 28, 2024

My point is that Apollo Server has nothing to do with this Promise. The very thing that (in a sense) makes Apollo Server/graphql-js aware that this Promise is connected to the result of executing your resolver in any way is the fact that it gets a .catch or await applied to it while the function is running. As far as Apollo Server is concerned, this Promise might have been constructed in some completely unrelated piece of code, say some background setInterval or anything else. Whenever you create a Promise in JS, it's your job to tell Node that you're paying attention to rejections on that Promise before you yield to the event loop (by awaiting, say), not afterwards. This is what functions like Promise.all are for. Apollo Server doesn't somehow hijack the creation of Promises within resolvers to do something extra fancy to them to handle their rejections — you're expected to set your Promises up properly like in any other async code.

As far as Node knows, your function may have just been:

const myResolverC = async () => {
  const x = thisPromiseWillReject();
  await thisPromiseWillResolveAfterALongTime();
}

because it never actually gets to the point of running your await x before x rejects. And since there's no await here, it's not correct for myResolverC's returned Promise to reject just because x rejects — you haven't told the JS runtime to do that!

from apollo-server.

mfanegg avatar mfanegg commented on April 28, 2024

I see what the difference is now -- throw Error is picked up by the async resolver function as a promise rejection, but a promise with no await is just dangling in a sense so the async resolver function would never see it as you noted, and just resolve to undefined.

Thanks for explaining that to me, I will relay it.

from apollo-server.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.