1
votes

I'm using Amadeus API for flight search. Amadeus API requires that requests must not be more frequent than 1/100ms. I use the following code to limit the request frequency

        for depDate in depDates{
            DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
                AmadeusHelper.sharedInstance().searchAFlight(depAirport: depAirport, arrAirport: arrAirport, depDate: depDate, airlineCode: airlineCode, flightNumber: flightNum) { result in
                    switch result{
                    case .failure(let err):
                        print(err)
                    case .success(let flightOffer):
                        if let json = flightOffer.get(){
                            flightCandidate.offers?.append(json)
                        }
                    }
                    
                }
            }
        }

The above for-loop runs 4 loops. The searchAFlight function is essentially a wrapper of HTTP request. asyncAfter function will delay each request by 1 second. I thought this would be enough. However, I still get tooManyRequests error. When I was debugging, I followed the code step by step, which should took much longer to send out the requests. The requests should spread out across several minutes, long enough to satisfy the 100ms interval. Did I do something wrong here? Thanks.

===========Update=========

Based on Eric's comments, I changed the code to the following

        var timeNow = DispatchTime.now() // each request is delayed with an increasing interval 
        for (index, depDate) in depDates.enumerated(){
            DispatchQueue.main.asyncAfter(deadline: timeNow + Double(1*index)) {
                AmadeusHelper.sharedInstance().searchAFlight(depAirport: depAirport, arrAirport: arrAirport, depDate: depDate, airlineCode: airlineCode, flightNumber: flightNum) { result in
                    switch result{
                    case .failure(let err):
                        print(err)
                    case .success(let flightOffer):
                        if let json = flightOffer.get(){
                            flightCandidate.offers?.append(json)
                        }
                    }
                    
                }
            }
        }

Unfortunately, I still get tooManyRequest error.

1
All DispatchQueue.main.asyncAfter(deadline: .now() + 1) is doing is that it offsets the moment the requests will start, but they still are executed very fast in sequence (because of the loop) once the first one has started (they're all offset with the same time). - Eric Aya
Thanks @EricAya Do you have any suggestion for doing the four requests with required intervals among them? Timer? - Feng
Hi @EricAya I tried single requests (not using semaphore, but just send only one request), it works fine. I will try semaphore and timer. But one thing here is that, the API sometimes doesn't call the completion handler. The example you gave has a semaphore.signal() in the completion handler. Any suggestion to add some timeout logic here? Thanks! - Feng

1 Answers

0
votes

Your first attempt was:

for depDate in depDates {
    DispatchQueue.main.asyncAfter(deadline: .now() + 1) { ... }
}

That will not work because you are scheduling all of those iterations to start one second from now, not one second from each other.

You then attempted:

for (index, depDate) in depDates.enumerated() {
    DispatchQueue.main.asyncAfter(deadline: .now() + Double(index)) { ... }
}

That is likely closer to what you want, but is subject to “timer coalescing”, a feature where the OS will start grouping/coalescing dispatched blocks together. This is a great power saving feature, but will circumvent the desire to have delays between the requests. Also, you’re going to have troubles if you ever want to cancel some of these subsequent requests (without complicating the code a bit, at least).


The simplest solution is to adopt a recursive pattern, where you trigger the next request in the completion handler of the prior one.

func searchFlight(at index: Int = 0) {
    guard index < depDates.count else { return }

    let depDate = depDates[index]

    AmadeusHelper.sharedInstance().searchAFlight(depDate: depDate, ...) { result in
        defer {
            if index < (depDates.count - 1) {
                DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in 
                    self?.searchFlight(at: index + 1)
                }
            }
        }

        switch result { ... }
    }
}

There’s no coalescing of these calls. Also, this has the virtue that the delay for a subsequent request will be based upon when the prior one finished, not after the prior one was issued (which can be an issue if scheduling a bunch of these requests up-front).

That having been said, I would advise that you direct this inquiry to Amadeus. I wouldn’t be surprised if this 100ms limitation was introduced to prevent people from mining their database through their API. I also wouldn’t be surprised if they have other, undocumented, techniques for identifying excessive requests (e.g. number of requests per hour, per 24 hours, etc.). The question of “why am I receiving tooManyRequest error” is best directed to them.