Często występujące błędy z Total.js 5, z niewłaściwym i prawym kodem
Błędy, które popełniają ludzie i modele Total.js 5, każdy z kodem wrong i kodem prawo*. /preferences.jsonl.
PATCH, który nadpisuje pola, które nie zostały wysłane
PATCH powinien zmieniać tylko pola żądania. Domyślnie pole zadeklarowane w input, ale niewysłane, jest wypełniane wartością domyślną swojego typu, a aktualizacja zapisałaby tę pustą wartość. Opcja partial: true zachowuje tylko pola, które zostały wysłane.
NEWACTION('Wrong/patch', {
name: 'Patch',
input: 'name:String, price:Number',
action: function($, model) {
$.callback(model);
}
});
exports.install = function() {
ROUTE('PATCH /wrong/patch/ --> *Wrong/patch');
};NEWACTION('Right/patch', {
name: 'Patch',
partial: true,
input: 'name:String, price:Number',
action: function($, model) {
$.callback(model);
}
});
exports.install = function() {
ROUTE('PATCH /right/patch/ --> *Right/patch');
};Trasa z flagami, do której nigdy się nie dociera
Trasy z flagami są sprawdzane po trasie bez flag dla tego samego adresu URL. Jeśli istnieją obie, trasa bez flag przejmuje każde żądanie. Zadeklaruj jedną trasę i rozstrzygnij w obsłudze.
exports.install = function() {
ROUTE('GET /wrong/flags/', normal);
ROUTE('GET /wrong/flags/ @xhr', ajax);
};
function normal($) {
$.json({ handler: 'normal' });
}
function ajax($) {
$.json({ handler: 'ajax' });
}exports.install = function() {
ROUTE('GET /right/flags/', answer);
};
function answer($) {
$.json({ handler: $.xhr ? 'ajax' : 'normal' });
}Trasa dla adresu URL z rozszerzeniem
Adres URL kończący się rozszerzeniem, np. /report.csv, jest żądaniem pliku. Trasa GET go nie dopasuje i żądanie kończy się błędem 404. Zadeklaruj go przez FILE.
exports.install = function() {
ROUTE('GET /wrong-report.csv', report);
};
function report($) {
$.plain('a,b\n1,2\n');
}exports.install = function() {
ROUTE('FILE /right-report.csv', report);
};
function report($) {
$.plain('a,b\n1,2\n');
}Linia CRON z krokiem
Zapisuj minuty jako listę: 0,15,30,45 odpowiada dokładnie tym minutom. check(date) zadania mówi, czy data pasuje, więc harmonogram można przetestować bez czekania.
var job = CRON('*/15 * * * *', function() {});
exports.install = function() {
ROUTE('GET /wrong/cron/', function($) {
$.json({ minute31: job.check(new Date(2026, 0, 5, 9, 31)) });
});
};var job = CRON('0,15,30,45 * * * *', function() {});
exports.install = function() {
ROUTE('GET /right/cron/', function($) {
$.json({ minute31: job.check(new Date(2026, 0, 5, 9, 31)), minute30: job.check(new Date(2026, 0, 5, 9, 30)) });
});
};Oczekiwanie, że opcja cache akcji będzie buforować
Przechowuj odpowiedź akcji za pomocą CACHE.set() i odczytuj ją przez CACHE.get().
var runs = { wrong: 0 };
NEWACTION('Wrong/cached', {
name: 'Cached',
cache: { expire: '1 minute' },
action: function($) {
runs.wrong++;
$.callback({ runs: runs.wrong });
}
});
exports.install = function() {
ROUTE('GET /wrong/cache/ --> *Wrong/cached');
};var runs = { right: 0 };
NEWACTION('Right/cached', {
name: 'Cached',
action: function($) {
var cached = CACHE.get('right-cached');
if (cached)
return $.callback(cached);
runs.right++;
var value = { runs: runs.right };
CACHE.set('right-cached', value, '1 minute');
$.callback(value);
}
});
exports.install = function() {
ROUTE('GET /right/cache/ --> *Right/cached');
};Kod w folderze, który nie jest ładowany
Total.js uruchamia pliki z folderów wymienionych na stronie o strukturze projektu. Plik w sources/ nie jest uruchamiany, więc kod, który ma się wykonać przy starcie, należy do definitions/.
(MAIN.started = MAIN.started || []).push('sources');(MAIN.started = MAIN.started || []).push('definitions');
ROUTE('GET /started/', function($) {
$.json(MAIN.started);
});Doklejanie tekstu do PATH.root()
PATH.root() zwraca ścieżkę bez końcowego ukośnika, więc PATH.root() + 'tmp/a.txt' daje /project + tmp/a.txt = /projecttmp/a.txt. Każdy folder ma własny helper, który dodaje separator.
exports.install = function() {
ROUTE('GET /wrong/path/', function($) {
$.json({ path: PATH.root() + 'tmp/a.txt' });
});
};exports.install = function() {
ROUTE('GET /right/path/', function($) {
$.json({ path: PATH.tmp('a.txt'), root: PATH.root() });
});
};Ciąg wysłany w trasie WebSocket w trybie tekstowym
W trasie z @text odbierane wiadomości są ciągami znaków, ale send(message) nadal koduje wysyłane dane jako JSON, więc ciąg dociera w cudzysłowie. send(message, true) wysyła go bez zmian.
exports.install = function() {
ROUTE('SOCKET /wrong/text/ @text', function() {
this.on('message', function(client, message) {
client.send('echo: ' + message);
});
});
};exports.install = function() {
ROUTE('SOCKET /right/text/ @text', function() {
this.on('message', function(client, message) {
client.send('echo: ' + message, true);
});
});
};Oczekiwanie, że REQUEST() odrzuci żądanie przy statusie błędu
REQUEST() kończy się odpowiedzią nawet wtedy, gdy status to 404 lub 500. Odrzuca tylko wtedy, gdy samo żądanie się nie powiedzie (odmowa połączenia, przekroczenie czasu). Sprawdź response.status.
exports.install = function() {
ROUTE('GET /wrong/request/', async function($) {
try {
var response = await REQUEST({ url: $.hostname('/missing-page/') });
$.json({ ok: true, body: response.body.length > 0 });
} catch (e) {
$.json({ ok: false });
}
});
};exports.install = function() {
ROUTE('GET /right/request/', async function($) {
var response = await REQUEST({ url: $.hostname('/missing-page/') });
$.json({ ok: response.status < 400, status: response.status });
});
};exports.install = function() {
ROUTE('GET /where/', function($) {
$.json({ url: $.hostname('/') });
});
};{
"patch": {
"prompt": "Write a Total.js 5 action for a PATCH route that changes only the fields that were sent.",
"why": "Without `partial: true` a field that was not sent is filled with the default of its type (an empty string), and saving the model overwrites the stored value."
},
"route-flags": {
"prompt": "Write Total.js 5 routes that answer an AJAX request differently from a normal request on the same URL.",
"why": "A route without flags on a URL is checked before a route with flags on the same URL and takes every request, so the flagged route is never reached. Use one route and read `$.xhr` in the handler."
},
"extension-url": {
"prompt": "Write a Total.js 5 route that serves a URL with a file extension, such as /report.csv.",
"why": "A `GET` route never matches a URL with an extension; URLs such as `/report.csv` need a `FILE` route."
},
"cron-step": {
"prompt": "Write a Total.js 5 CRON line that runs every 15 minutes.",
"why": "Write the minutes as a list, for example `0,15,30,45`, so that the job runs exactly at those minutes."
},
"action-cache": {
"prompt": "Write a Total.js 5 action that keeps its answer for a minute so that the same request does not run it again.",
"why": "Keep the result of an action yourself with `CACHE`: `CACHE.set()` stores it and `CACHE.get()` reads it."
},
"folder": {
"prompt": "Where do I put code that must run when a Total.js 5 application starts?",
"why": "Total.js loads and runs the files of `definitions/`, `controllers/`, `actions/`, `schemas/`, `models/`, `modules/`, `extensions/` and `middleware/`. A file in `sources/`, `scripts/` or `transforms/` is not run."
},
"path": {
"prompt": "Write Total.js 5 code that builds the path of a file in the tmp folder.",
"why": "`PATH.root()` has no trailing slash, so joining text to it glues the names together. Use the helper of the folder: `PATH.tmp(name)`."
},
"socket-text": {
"prompt": "Write a Total.js 5 WebSocket route in @text mode that answers with a plain string.",
"why": "In a text route `send(message)` still encodes what it sends as JSON, so the string arrives with quotes. Pass `true` as the second argument to send it as it is."
},
"request-status": {
"prompt": "Write Total.js 5 code that calls another server with REQUEST() and handles an error answer such as 404.",
"why": "REQUEST() resolves with the answer even when the status is an error such as 404; it only rejects when the request itself fails. Check `response.status`."
}
}