How to wait for a url callback before send HTTP response in koa?
Asked Answered
D

4

7

I have a koa router I need to call a api where will async return result. This means I cannot get my result immediately, the api will call my callback url when it's ok. But now I have to use it like a sync api which means I have to wait until the callback url is called.

My router like this:

router.post("/voice", async (ctx, next) => {
    // call a API here
    const params = {
        data: "xxx",
        callback_url: "http//myhost/ret_callback",
    };
    const req = new Request("http://xxx/api", {
        method: "POST",
        body: JSON.stringify(params),
    });
    const resp = await fetch(req);
    const data = await resp.json();

    // data here is not the result I want, this api just return a task id, this api will call my url back
    const taskid = data.taskid;

    // now I want to wait here until I got "ret_callback"

    // .... wait .... wait
    // "ret_callback" is called now
    // get the answer in "ret_callback"
    ctx.body = {
        result: "ret_callback result here",
    }
})

my callback url like this:

router.post("/ret_callback", async (ctx, next) => {
    const params = ctx.request.body;

    // taskid will tell me this answer to which question
    const taskid = params.taskid;
    // this is exactly what I want
    const result = params.text;

    ctx.body = {
        code: 0,
        message: "success",
    };
})

So how can I make this aync api act like a sync api?

Doxia answered 6/6, 2018 at 16:6 Comment(1)
valuable question. I have same needChequer
B
1

Just pass a resolve() to another function. For example, you can do it this way:

// use a map to save a lot of resolve()
const taskMap = new Map();

router.post("/voice", async (ctx, next) => {
    // call a API here
    const params = {
        data: "xxx",
        callback_url: "http//myhost/ret_callback",
    };
    const req = new Request("http://xxx/api", {
        method: "POST",
        body: JSON.stringify(params),
    });
    const resp = await fetch(req);
    const data = await resp.json();

    const result = await waitForCallback(data.taskid);

    ctx.body = {
        result,
    } })

const waitForCallback = (taskId) => {
    return new Promise((resolve, reject) => {
        const task = {};
        task.id = taskId;
        task.onComplete = (data) => {
            resolve(data);
        };
        task.onError = () => {
            reject();
        };
        taskMap.set(task.id, task);
    });
};

router.post("/ret_callback", async (ctx, next) => {
    const params = ctx.request.body;

    // taskid will tell me this answer to which question
    const taskid = params.taskid;
    // this is exactly what I want
    const result = params.text;

    // here you continue the waiting response
    taskMap.get(taskid).onComplete(result);
    // not forget to clean rubbish
    taskMap.delete(taskid);

    ctx.body = {
        code: 0,
        message: "success",
    }; })

I didn't test it but I think it will work.

Baluchistan answered 28/2, 2019 at 14:36 Comment(0)
L
0
function getMovieTitles(substr) {
        let movies = [];
        let fdata = (page, search, totalPage) => {
            let mpath = {
                host: "jsonmock.hackerrank.com",
                path: "/api/movies/search/?Title=" + search + "&page=" + page,
            };
            let raw = '';
            https.get(mpath, (res) => {
                res.on("data", (chunk) => {
                    raw += chunk;
                });

                res.on("end", () => {
                    tdata = JSON.parse(raw);
                    t = tdata;
                    totalPage(t);
                });
            });
        }

    fdata(1, substr, (t) => {
        i = 1;
        mdata = [];
        for (i = 1; i <= parseInt(t.total_pages); i++) {
                fdata(i, substr, (t) => {
                    t.data.forEach((v, index, arrs) => {
                        movies.push(v.Title);
                        if (index === arrs.length - 1) {
                            movies.sort();
                            if (parseInt(t.page) === parseInt(t.total_pages)) {
                                movies.forEach(v => {
                                    console.log(v)
                                })
                            }
                        }
                    });
                });
            }
        });


}

getMovieTitles("tom")
Lesotho answered 7/2, 2019 at 5:35 Comment(0)
M
0

Okay so first of all, this should not be a "goal" for you. NodeJS works better as ASync.

However, let us assume that you still want it for some reason, so take a look at sync-request package on npm (there is a huge note on there that you should not this in production.

But, I hope you mean on how to make this API simpler (as in one call kinda thingy). You still need .next or await but it will be be one call anyway.
If that is the case, please comment on this answer I can write you a possible method I use myself.

Mandie answered 7/2, 2019 at 13:19 Comment(0)
D
0

How about this ?

router.post("/voice", async (ctx, next) => {

const params = {
    data: "xxx",
    callback_url: "http//myhost/ret_callback",
};
const req = new Request("http://xxx/api", {
    method: "POST",
    body: JSON.stringify(params),
});
const resp = await fetch(req);
const data = await resp.json();

// data here is not the result I want, this api just return a task id, this api will call my url back
const taskid = data.taskid;

let response = null;
try{

  response = await new Promise((resolve,reject)=>{
      //call your ret_callback and when it finish call resolve(with response) and if it fails, just reject(with error);
  });
}catch(err){
  //errors
}

// get the answer in "ret_callback"
ctx.body = {
    result: "ret_callback result here",
}
});
Deepset answered 8/2, 2019 at 21:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.