0

I'm having a hard time to sequence my API calls. so I used then() chaining to sequence them in order. All the API and the refresh token are Promises/Async. It's working but is there a cleaner/fancier/shorter way to this without using async/await because my parent function is not async. I don't fully understand the behavior of .then() and async/await

Here is the code inside the parent function:

 refreshToken().then(token => {
                let request = {} //format request

                return axios.post(`${youtubeUrl}/upload/youtube/v3/videos?access_token=${token}&part=contentDetails`, request) //upload video
            })
            .then(uploadResponse => {
                let uploadResponse = {}; //format uploadResponse

                refreshToken().then(token => { //refresh the token again
                    return axios.put(`${youtubeUrl}?access_token=${token}`, uploadResponse) //update existing video
                })
                .then(updateResponse => {
                    let updateResponse = {}; //format updateResponse

                    axios.post(`${BasePath}/v1/videos`, updateResponse, headers)
                    .then(postResponse => {
                        if (postResponse.data.response === 'success') {
                            return dispatch(receivePostsData(postResponse.data))
                        } else if (postResponse.data.response === 'failed') return dispatch(receivePostsData(postResponse.data))
                    })
                })
            })    
            .catch(error => {
                return dispatch(receivePostsData(error))
            })
Makopa
  • 91
  • 9
  • You can make the first callback function async, eg: `.then(async (token) => {.....})` – Titus Oct 14 '19 at 17:33
  • 5
    Can you not use all your `.then`s in a flat way? `doThis().then().then().then().then()` etc. That's what Promises should be all about. – VLAZ Oct 14 '19 at 17:34
  • 3
    Promises are chainable – AngelSalazar Oct 14 '19 at 17:34
  • Wait, you do `refreshToken()` and as part of handling the result, you also call `refreshToken()`? I've not finished reading the code but this seems like a code smell. I don't think the design is very good if you need to re-call the same thing you're responding to. – VLAZ Oct 14 '19 at 17:36
  • @Titus doesn't matter if it's async or not - it returns a promise, so it's basically the same thing. – VLAZ Oct 14 '19 at 17:37
  • @VLAZ The OP mentioned that the parent function is not `async`, I'm just letting him know that he can make a callback function `async`. – Titus Oct 14 '19 at 17:38
  • 1
    @VLAZ: the nesting uses closure, so it can't be completely flattened, though some of the nesting could be removed, and globals could flatten it completely. – dandavis Oct 14 '19 at 17:39
  • @VLAZ hi. I need to re-invoke the refreshToken() function to get a new fresh token. cause the token has expiration. Since upload can take sometime defends on the filesize, so i need to refresh the token again after the uploads then do the next action – Makopa Oct 14 '19 at 17:40
  • @VLAZ hi there. I need to format the response and use it as request body to my another API call so I need to do the API calling inside the then() and there's where the nested happens :( – Makopa Oct 14 '19 at 17:42
  • Have a look at [How do I access previous promise results in a .then() chain?](https://stackoverflow.com/q/28250680/1048572) - but really, as everyone said, just *make* your parent function `async`. Or just use an [IIAFE](https://stackoverflow.com/a/40746608/1048572). – Bergi Oct 14 '19 at 17:50
  • 1
    use `return` more. – Kevin B Oct 14 '19 at 17:52
  • 1
    @Makopa By wrapping your logical steps into a few functions, some [destructoring](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment) and implicit returns, the .then chain can be written in 7 easily readable lines like [this](https://repl.it/repls/SparseSunnySearchengine) – r.delic Oct 14 '19 at 18:08
  • thanks @r.delic ! new heard or destructoring before. very useful to catch the desginated arguement in function – Makopa Oct 14 '19 at 18:23

2 Answers2

2

With aysnc await you can convert your callback hell to this:

important notes are:

  • async keyword before the function allows to use await
  • To handle exceptions you need to use try catch block.
async function uploadToYoutube() {

    try {
        let token = await refreshToken();

        let request = {}

        const youtubeUploadResponse = await axios.post(`${youtubeUrl}/upload/youtube/v3/videos?access_token=${token}&part=contentDetails`, request);

        let uploadResponse = {};

        token = await refreshToken();

        const youtubeUpdateResponse = await axios.put(`${youtubeUrl}?access_token=${token}`, uploadResponse);

        let updateResponse = {};

        let postResponse = await axios.post(`${BasePath}/v1/videos`, updateResponse, headers);

        if (postResponse.data.response === 'success') {
            return dispatch(receivePostsData(postResponse.data))
        } else if (postResponse.data.response === 'failed') {
            //??? why do you here act like a success?
            return dispatch(receivePostsData(postResponse.data))
        }
    } catch (error) {
        //??? why do you here act like a success?
        return dispatch(receivePostsData(error))
    }
}
SuleymanSah
  • 17,153
  • 5
  • 33
  • 54
  • This is so much cleaner. By the way the parent function will be used as action in my react redux app. Is not gonna be a problem? not really familiar with it at the moment – Makopa Oct 14 '19 at 17:55
  • 1
    try and see what happens :) if you have a problem there, you may ask another question. But this is the way to get rid of callback hell. – SuleymanSah Oct 14 '19 at 17:58
  • thank you. will try and also to help me fully understand it! – Makopa Oct 14 '19 at 18:02
  • hi there last question. does the catch block can catch the first error in any of those API if they fail? – Makopa Oct 14 '19 at 18:03
  • @Makopa yes it will catch the first error. – SuleymanSah Oct 14 '19 at 18:04
1

If you are using ES6 and above you can use async await