19

I have a React Native API call I am making.

Theoretically it should work -

    import API from "../../utils/API";

  componentDidMount() {
    let merchantId = this.props.merchant.id;
    let api = new API(this.props.gatheredTokens);
    let self = this;
    api.setRetry(10);
    api
      .get("merchantMessages", { repl_str: merchantId })
      .then(response => this.merchantMessageConfiguration(response.data))
      .catch(function (error) {
        console.log(error);
      })
      .finally(function () {
        self.state.list.push(
          <Card
            merchant={self.props.merchant}
            key={self.props.merchant.id}
            bubblemsg={self.state.bubblemsg}
          />
        );
      })
      .finally(function () {
        self.merchantNoticeLoading(self);
      });
  }

However I am getting the following error:

TypeError

What is causing this error? The code looks valid.

Here is what get is:

 get(API, params = this.defaultParams) {
    this.call = "GET";
    let constructedURL = this.constructURL(API, params);
    axiosRetry(axios, { retries: this.retry });
    return axios.get(constructedURL, this.config);
  }
Steven Matthews
  • 9,705
  • 45
  • 126
  • 232
  • 2
    Does `.get` return a Promise-like object that implements the `.finally` method? It sounds like it doesn't (in environments that support `.finally`, it's only guaranteed to exist on *native Promises* constructed with `new Promise`, I believe - which is a pretty unfortunate source of confusion) – CertainPerformance Sep 11 '19 at 06:22
  • Get is just a wrapper for an axios call. I'll add it. – Steven Matthews Sep 11 '19 at 06:23
  • 1
    The explicit Promise construction antipattern could solve it, but I bet there's a better way – CertainPerformance Sep 11 '19 at 06:26
  • @nivendha .get is a method of API – Steven Matthews Sep 11 '19 at 06:27
  • @CertainPerformance - oh there's specific environmental rules when it comes to finally? – Steven Matthews Sep 11 '19 at 06:27
  • The only place `.finally` is in the specification is [Promise.prototype.finally()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/finally). But there are many thenables (often exposed by libraries) which do *not* inherit from `Promise.prototype`, which means that although you can call `.then` and `.catch` on them as if it was a native Promise, it's not a native Promise, so you can't use `.finally` with them. – CertainPerformance Sep 11 '19 at 06:40

9 Answers9

28

I suggest to use another then instead of using finally. then after catch is works like a finally. don't forget to use at least one catch in your promise chain, in order to handle your instructions failure.

So this two line of code is the same:

api.get(…).then(…).catch(…).then(...)

and

api.get(…).then(…).catch(…).finally(...)
Sadegh Teimori
  • 1,348
  • 12
  • 12
  • 7
    This works. But why can't I use finally() if I'm developing a modern application for modern browsers and this method is documented in MDN? – Wilson Silva May 09 '20 at 23:07
  • @Sadegh Teimori the `.then()` block(which is being used as finally) after `.catch()` doesn't get called when the execution goes into `.catch()`. Any solution? – PrashantNagawade May 12 '20 at 07:24
  • 1
    @Sadegh Why are you suggesting this? Is something wrong with `.finally()` in modern browsers? – MEMark May 15 '20 at 17:19
  • 1
    `.finally()` doesn't supported by all of browsers. To ensure my code works on the all browser, i always use `.catch()` followed by `.then()`, instead of `.finally()`. – Sadegh Teimori May 16 '20 at 14:57
  • In my case Jimp resets Promise.prototype to ES5 level, so `finally` presents at app start and it is gone after jimp import – Anton Oct 28 '22 at 18:07
8

Only a native Promise (constructed with new Promise) is guaranteed to have a .finally method (in newer environments). (in older environments, .finally won't work at all with Promises created with new Promise)

It looks like axios doesn't use new Promise internally - rather, it just returns a thenable, which is not guaranteed to have a finally method (and because it doesn't, it throws an error).

While you could use the explicit Promise construction antipattern to wrap the axios call in a native new Promise, so that it has Promise.prototype.finally in its prototype chain, a better option (thanks Bergi!) is to just use Promise.resolve, which will turn a thenable into a native Promise while preserving the failure or success of the thenable:

get(API, params = this.defaultParams) {
  this.call = "GET";
  let constructedURL = this.constructURL(API, params);
  axiosRetry(axios, { retries: this.retry });
  return Promise.resolve(axios.get(constructedURL, this.config));
}
CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
  • 2
    You still should avoid the [`new Promise` constructor antipattern](https://stackoverflow.com/q/23803743/1048572?What-is-the-promise-construction-antipattern-and-how-to-avoid-it). To coerce a thenable to a native promise, write `return Promise.resolve(axios.get(…));`, nothing more. – Bergi Jan 14 '21 at 17:10
  • Oh, so `Promise.resolve` will allow rejections to pass through without causing issues, very cool! Thanks – CertainPerformance Jan 15 '21 at 00:23
1

The promise should be taking finally of es6, which I am not sure is something that gets supported by the axios promise, if I may, I sugest use then instead of finally

import API from "../../utils/API";

componentDidMount() {
let merchantId = this.props.merchant.id;
let api = new API(this.props.gatheredTokens);
let self = this;
api.setRetry(10);
api
  .get("merchantMessages", { repl_str: merchantId })
  .then(response => this.merchantMessageConfiguration(response.data))
  .catch(function (error) {
    console.log(error);
  })
  .then(function () {
    self.state.list.push(
      <Card
        merchant={self.props.merchant}
        key={self.props.merchant.id}
        bubblemsg={self.state.bubblemsg}
      />
    );
  })
  .then(function () {
    self.merchantNoticeLoading(self);
  });
 }
nivendha
  • 792
  • 7
  • 16
  • use `require('promise.prototype.finally')` if u need the finally support , `finally` function will added to the `Promise.prototype` – nivendha Sep 11 '19 at 06:37
  • the `.then()` after `.catch()` doesn't get called when the execution goes into `.catch()`. Any solution? – PrashantNagawade May 12 '20 at 07:23
1

You can use a npm package promise.prototype.finally for polyfill.

doing like this:

var promiseFinally = require('promise.prototype.finally');
promiseFinally.shim();

or in TypeScript:

//@ts-expect-error
import promiseFinally from 'promise.prototype.finally';

promiseFinally.shim();

The package will override the function if Promise.prototype.finally is not found in user browser. And if the browser support Promise.prototype.finally, it will just use the native function.

I use Create React App for building frontend project. It seems that Babel and TypeScript won't manage with this tough things, you should deal with it on your own.

Yari
  • 151
  • 2
  • 6
0

Pass the api in your get function like this:

 import API from "../../utils/API";

  componentDidMount() {
    let merchantId = this.props.merchant.id;
    let api = new API(this.props.gatheredTokens);
    let self = this;
    api.setRetry(10);

     get(api + "/merchantMessages", { repl_str: merchantId })
      .then(response => this.merchantMessageConfiguration(response.data))
      .catch(function (error) {
        console.log(error);
      })
      .finally(function () {
        self.state.list.push(
          <Card
            merchant={self.props.merchant}
            key={self.props.merchant.id}
            bubblemsg={self.state.bubblemsg}
          />
        );
      })
      .finally(function () {
        self.merchantNoticeLoading(self);
      });
  }
Akram Badah
  • 417
  • 3
  • 12
0

I have had a similar problem once but it was exclusive with Firefox, but I still needed to do some code after resolving or rejection, so I used async/await form.

import API from "../../utils/API";

componentDidMount() {
let merchantId = this.props.merchant.id;
let api = new API(this.props.gatheredTokens);
let self = this;
api.setRetry(10);
(async () => {
    try {
        const response = await api.get("merchantMessages", { repl_str: merchantId })
        this.merchantMessageConfiguration(response.data)
    } catch (error) {
        console.log(error);
    } finally {
        self.state.list.push(
            <Card
                merchant={self.props.merchant}
                key={self.props.merchant.id}
                bubblemsg={self.state.bubblemsg}
            />
       );
    }  
})()
}
corvus
  • 513
  • 1
  • 3
  • 13
0
var url = 'https://api.github.com/users/hadley/orgs';
fetchJson(url)
.then((obj) =>{ 
    console.log('obj') 
    console.log(obj) 
    console.log('obj') 
})
.catch((error) =>{ 
    console.log('error') 
    console.log(error) 
    console.log('error') 
})
.finally(() => { 
    console.log('WHALE HELLO THERE ') 
});
Mohit Sharma
  • 529
  • 1
  • 6
  • 17
0

You could add this somewhere in the beginning of your code:

if (Promise.prototype.finally == null) {
    Promise.prototype.finally = function (handler) {
        return this.then(handler, handler);
    }
}

Fiddle to test it: https://jsfiddle.net/m63kgq7f/

Jose Enrique
  • 361
  • 1
  • 3
  • 8
-6

try to avoid using .finally(). That's it.