@StephenChapman, thanks so much for helping me out, here! I should have posted my code straight away — I guess I was hoping there was a magic way to get more detailed feedback from Knack.
My code is a bit complex, with a retry, but you can ignore most of that, I think. It executes straight through to the first fetch call and fails with status 400, so none of the retry logic is coming into play. If you need a simplified version I can provide that, but I’m out of time tonight, so I’ll just post as-is for now:
this.createOptionsBody = function(newRank, newAdjRank) {
if (this.rankFieldId && this.adjRankFieldId) {
return {
[this.rankFieldId]: newRank,
[this.adjRankFieldId]: newAdjRank,
};
} else if (this.rankFieldId) {
return {
[this.rankFieldId]: newRank,
};
} else if (this.adjRankFieldId) {
return {
[this.adjRankFieldId]: newAdjRank,
};
} else {
throw new Error('At least one of this.rankFieldId and this.adjRankFieldId must be set');
}
}.bind(this);
// Returns the number of milliseconds to wait before retrying, or zero if success (no retry needed), or throws
this.putRankToDatabase = async function(url, options, retryNumber) {
const response = await fetch(url, options); // returns response
console.log(`Fetch returned`);
const hdrs = response.headers;
console.log(hdrs);
const planLimitRemaining = Number(hdrs.get("X-PlanLimit-Remaining"));
console.log(`planLimitRemaining = ${planLimitRemaining}`);
if (response.ok) {
console.log(`response.ok`);
return 0;
} else if (response.status != 429) { // Status 429 is Too Many Requests
const text = await response.text();
console.log(`HTTP response code ${response.status}, response text '${text}'`);
throw new Error(`HTTP response code ${response.status}, response text '${text}'`);
} else if (planLimitRemaining <= 0) { // Exceeding Knack's daily plan limit
console.log(`Exceeded Knack API plan limit`);
const planLimit = Number(hdrs.get("X-PlanLimit-Limit"));
const planLimitReset = Number(hdrs.get("X-PlanLimit-Reset"));
throw new Error(`Exceeded Knack API plan limit (${planLimit} requests per day). Will reset in ${planLimitReset} milliseconds.`);
} else if (retryNumber == 0) {
console.log(`Retry 0`);
// Exceeded Knack's API rate limit
const rateLimitReset = Number(hdrs.get("X-RateLimit-Reset"));
const milliSecToWait = (1000 * rateLimitReset) - Date.now() + 1;
console.log(`Exceeded Knack API rate limit. Waiting ${milliSecToWait} milliseonds for reset`);
return milliSecToWait;
} else if (retryNumber == 1) {
console.log(`Retry 1`);
// Exceeded Knack's API rate limit a second time
const milliSecToWait = 1100;
console.log(`Exceeded Knack API rate limit. Waiting second time for ${milliSecToWait} milliseonds for reset`);
return milliSecToWait;
} else {
console.log(`Retry too many`);
// Exceeded Knack's API rate limit more than twice
throw new Error(`Exceeded Knack API rate limit, but waiting hasn't helped`);
}
}.bind(this);
this.putRankToDatabaseWithRetry = async function(id, newRank, newAdjRank) {
const url = `https://api.knack.com/v1/pages/${this.sceneId}/views/${this.gridId}/records/${id}`;
const options = {
method: 'PUT',
//mode: 'cors',
//cache: 'no-store',
headers: {
'Content-Type': 'application/json',
'X-Knack-Application-Id': Knack.application_id,
'X-Knack-REST-API-KEY': 'knack',
'Authorization': Knack.getUserToken(),
//'Accept': 'application/json',
},
body: JSON.stringify(this.createOptionsBody(newRank, newAdjRank)),
};
console.log(`Put rank URL: ${url}`);
console.log(options);
for (let retryNumber = 0;; ++retryNumber) {
const milliSecToWait = await this.putRankToDatabase(url, options, retryNumber);
console.log(`Put rank returned. retry = ${retryNumber}, milliSecToWait = ${milliSecToWait}`);
if (milliSecToWait <= 0) {
break;
}
await this.sleep(milliSecToWait);
}
}.bind(this);
The top-level function is the one at the bottom, putRankToDatabaseWithRetry. I hope it’s not too hard to follow, but the first thing that function does is to build the URL and the options object for the call to fetch. The body is supplied by the first function, createOptionsBody. You can see that I’ve experimented with mode, cache directive, and accept header in the options, but those don’t seem to make a difference.
Any ideas you have for things I should try are welcome. Thanks in advance!