0

Let's say I have this code, and I have written a function called handleSmell() in a package that others can use:

const streamSomeUrl = (url='https://salmon.net/river') => {

  console.info('...connecting...');
  const { data, headers } = await axios({
    url,
    method: 'GET',
    responseType: 'stream',
  });
  return new Promise((resolve, reject) => {
    data.on('data', (chunk) => {
       if(smellsFunny(chunk)) {
         // promise aware function
         handleSmell(chunk) // <--- behaves differently inside a promise. 
         // E.g. prints  "Smell inside a promise" 
       }
    });
  })
}

Is there a way, that the function handleSmell to tell if it's in a promise or not? By that I mean, it would behave differently to here:

readFile('/etc/fish', (err, data) => {
  if(smellsFunny(data)) {
     // promise aware function
     handleSmell(data) // <--- behaves differently inside a promise.
     // E.g. prints  "Smell *not* inside a promise" 
     ...
     }
});

I'm trying to figure out if I can do with without taking reject/resolve callbacks in to the handleSmell function (and then if missing, know I'm not in a promise).

As far as I'm aware I can't use the fact that different data is passed through, both chunk and data are strings/buffers?

Is this at all possible? I'm motivated by this question, mainly this bit from an answer:

Any time you are inside of a promise callback, you can use throw. However, if you're in any other asynchronous callback, you must use reject.

new Promise(function() {
  setTimeout(function() {
    throw 'or nah';
    // return Promise.reject('or nah'); also won't work
  }, 1000);
}).catch(function(e) {
  console.log(e); // doesn't happen
});

My preference is to throw, but I want to know if I call a reject callback (i.e. from the parent reject) it will work:


function handleSmell(suspectData, reject=False) {
  const inPromise = //?????
  if (inPromise && reject) {
    return reject('SMELLY REJECT!')
  } else if (inPromise) {
    throw new Error('SMELLY THROWN!')
  } else { console.error('SMELLY NON-PROMISE!') }
}
// Both these should work
new Promise(function() {
  setTimeout(function() {
    handleSmell('cheese');
}).catch(function(e) {
  console.log(e); // doesn't try to throw
});

new Promise(function(resolve, reject) {
  setTimeout(function() {
    handleSmell('cheese', reject);
  }, 1000);
}).catch(function(e) {
  console.log(e); // this works
});

The alternative is promisifying the non-promise (as per link).

But can I avoid that if possible?

AncientSwordRage
  • 7,086
  • 19
  • 90
  • 173
  • Note you certainly can't just call a random Promise.reject, you need to reject _the promise you're in_. – jonrsharpe Jan 08 '22 at 22:34
  • 3
    I don't understand why you need a new Promise and don't use the axios promise. This is an anti-pattern – charlietfl Jan 08 '22 at 22:34
  • @jonrsharpe I must have misunderstood this part of one of the answers I read: `setTimeout(function() { throw 'or nah'; // return Promise.reject('or nah'); also won't work }, 1000);` – AncientSwordRage Jan 08 '22 at 22:36
  • @charlietfl because the function in writing won't always be used with axios – AncientSwordRage Jan 08 '22 at 22:37
  • @TheFool is the accepted answer to the question I linked not right then? – AncientSwordRage Jan 08 '22 at 22:38
  • 2
    What you've posted says "also won't work". Because it's rejecting a _new_ promise, with no connection to the outer one. – jonrsharpe Jan 08 '22 at 22:38
  • @jonrsharpe I assumed that was only in the non-promise timeout, because it says '*also* won't work'? – AncientSwordRage Jan 08 '22 at 22:41
  • @TheFool I'm aware of the first part of what you said. I was under the impression that calling `Promise.reject(reason)` bubbled up/threw a new error that the parent promise would handle? I may have misremembered/imagined that behaviour. – AncientSwordRage Jan 08 '22 at 22:46
  • You would only use `Promise.reject()` in a then() block (or possibly in a catch()) and need to return it so it goes to the next catch in the promise chain. This whole question has gotten quite convoluted. It's really not clear what you need to accomplish here. You have really not told us what handleSmell is doing that needs to be different in promise or non promise situations – charlietfl Jan 08 '22 at 22:50
  • @charlietfl I guess I'm trying to find out if I'm in a then or a promise body? – AncientSwordRage Jan 08 '22 at 22:53
  • The point of the `Promise` constructor is to convert some operation (e.g., function call) to a promise, so it can be handled asynchronously. It is quite odd to have a function that acts differently based on whether it's used in the `Promise` constructor or not, as it inverts this expectation. If the function just uses a `throw`, then the way to be converted is to use `try { fn() } catch (e) { reject(e) }` (or similar) within the executor. Because that's the point - it acts as an adapter to a promise. – VLAZ Jan 08 '22 at 22:56
  • @vlaz, I'll repeat what I asked on Bergi's answer: So there's no way to guard against it being called in, say, a time out? – AncientSwordRage Jan 08 '22 at 22:59
  • It's the call site that should be doing the error handling. And it's the same error handling in both cases - a `try/catch` block. Again, the point of the `Promise` constructor is to convert something to be a promise. And to do that, you need to specify how that promise gets fulfilled or rejected. It's not really up to the callee to handle that. The same way how a callee won't know *nor should care* whether or not it's called within a `setTimeout`. – VLAZ Jan 08 '22 at 23:04
  • It's also worth pointing out that you are trying to use a stream from axios but this is not currently supported: https://github.com/axios/axios/issues/479 – GenericUser Jan 08 '22 at 23:07
  • @GenericUser it's working in NodeJs for me though. – AncientSwordRage Jan 08 '22 at 23:14
  • It may be that NodeJS or axios has some default if the `responseType` isn't understood, but presently this won't work in the browser because "stream" is not a valid response type. Here is the full list: https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseType. Right now it looks like the fetch API is the only means of streaming: https://developer.mozilla.org/en-US/docs/Web/API/Streams_API/Using_readable_streams – GenericUser Jan 08 '22 at 23:26
  • 1
    @GenericUser this isn't being used in the browser. I have the [tag:NodeJs] because I'm using it in Node, the serverside cousin of ECMAScript. I took my code from a project that really does work, but it's based on this: https://futurestud.io/tutorials/download-files-images-with-axios-in-node-js "This tutorial is specifically for Node.js, because you’ll stream the image to a file on the disc. The streaming option isn’t supported in Axios when using the library in the browser." – AncientSwordRage Jan 08 '22 at 23:34

1 Answers1

3

Is this at all possible?

No, you cannot do that, and no, you shouldn't do that. What a function does should depend on its arguments, not on where it was called from.

I'm motivated by this question, mainly this bit from an answer:

Any time you are inside of a promise callback, you can use throw. However, if you're in any other asynchronous callback, you must use reject.

Well, handleSmell is not defined in a callback, it's a standalone synchronous function, so it should simply throw an exception.

The proper way to call such a function is either from within a try block, or from within a promise (then) callback.

new Promise((resolve, reject) => {
  setTimeout(() => {
    try {
      resolve(handleSmell('cheese'));
    } catch(err) {
      reject(err);
    }
  });
}).then(console.log, console.error);
new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('cheese');
  }, 1000);
}).then(handleSmell).then(console.log, console.error);
//      ^^^^^^^^^^^
// or .then(res => handleSmell(res)) for verbosity
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • So there's no way to guard against it being called in, say, a time out? – AncientSwordRage Jan 08 '22 at 22:56
  • No, there is no way to do that. And it's not on the `handleSmell` function: the programmer who *called* the function from the `setTimeout` asynchronous callback without proper error handling is the one who made the mistake, not the one who wrote `handleSmell`. – Bergi Jan 08 '22 at 22:57
  • I was hoping there'd be something I could do before writing code and releasing it I to the wild.... – AncientSwordRage Jan 08 '22 at 23:01
  • 2
    If you *know* that your function is likely going to be called in asynchronous callback like that of `setTimeout`, and people will want to use promises, you should just make the promisified method available as part of your library so they don't have to do it themselves and can't make that mistake. But otherwise, there's nothing you need to do. – Bergi Jan 08 '22 at 23:05
  • now *that* is a really helpful idea. – AncientSwordRage Jan 08 '22 at 23:14