如何在 koa 中发送 HTTP 响应之前等待 url 回调?

Posted

技术标签:

【中文标题】如何在 koa 中发送 HTTP 响应之前等待 url 回调?【英文标题】:How to wait for a url callback before send HTTP response in koa? 【发布时间】:2018-11-16 09:44:19 【问题描述】:

我有一个koa 路由器,我需要调用一个api,异步返回结果。这意味着我不能立即得到我的结果,如果没问题,api 会调用我的callback url。但是现在我必须像同步 api 一样使用它,这意味着我必须等到回调 url 被调用。

我的路由器是这样的:

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",
    
)

我的回调网址是这样的:

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",
    ;
)

那么我怎样才能让这个aync api 像sync api 一样工作呢?

【问题讨论】:

有价值的问题。我也有同样的需求 【参考方案1】:

只需将 resolve() 传递给另一个函数。例如,您可以这样做:

// 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",
    ; )

我没有测试它,但我认为它会起作用。

【讨论】:

【参考方案2】:

这个怎么样?

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",

);

【讨论】:

【参考方案3】:

好的,首先,这不应该是您的“目标”。 NodeJS works better as ASync.

但是,让我们假设您出于某种原因仍然想要它,因此请查看 sync-request package on npm(那里有一个很大的说明,您不应该在生产环境中使用它。

但是,我希望您的意思是如何使这个 API 更简单(就像在一次调用中那样)。您仍然需要.nextawait,但无论如何这将是一个电话。 如果是这种情况,请对此答案发表评论,我可以为您写一个我自己使用的可能方法。

【讨论】:

【参考方案4】:
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")

【讨论】:

以上是关于如何在 koa 中发送 HTTP 响应之前等待 url 回调?的主要内容,如果未能解决你的问题,请参考以下文章

如何等待http请求在处理到angular 7中的下一步之前获得响应[重复]

Camel-netty4:如何在发送下一个请求之前等待响应

如何让任务在 Swift 3 中等待完成

在加载表单之前等待异步函数(Http 请求)

在koa中想要优雅的发送响应?看这就对了

如何在 PNaCl 中等待 WebSocket 响应