function completeJob(loader, job, callback) {
    if (job.errorMsg && job.data === null && job.tries < 1 + loader.tileRetryMax) {
        loader.failedTiles.push(job);
    }
    let nextJob;

    loader.jobsInProgress--;

    if (loader.canAcceptNewJob() && loader.jobQueue.length > 0) {
        nextJob = loader.jobQueue.shift();
        nextJob.start();
        loader.jobsInProgress++;
    }

    if (loader.tileRetryMax > 0 && loader.jobQueue.length === 0) {
        if (loader.canAcceptNewJob() && loader.failedTiles.length > 0) {
            nextJob = loader.failedTiles.shift();
            setTimeout(function () {
                nextJob.start();
            }, loader.tileRetryDelay);
            loader.jobsInProgress++;
        }
    }

    callback(job.data, job.errorMsg, job.request, job.dataType);
}
Cleans up ImageJob once completed. Restarts job after tileRetryDelay seconds if failed
but max tileRetryMax times
@method
@private
@param loader - ImageLoader used to start job.
@param job - The ImageJob that has completed.
@param callback - Called once cleanup is finished.