Originally published on php-to-js.learnio.dev — part of the PHP to JavaScript course. View this lesson on the original site.
Design functions so errors are obvious at the call site
In PHP you often throw exceptions or return null and hope callers check. Error-first design returns a structured result: success with data, or failure with a reason — especially in JavaScript helpers that feed UI code.
PHP habit
<?php
function parsePort(string $value): array {
if ($value === '') {
return ['ok' => false, 'error' => 'Port required'];
}
if (!ctype_digit($value)) {
return ['ok' => false, 'error' => 'Port must be a number'];
}
$port = (int) $value;
if ($port < 1 || $port > 65535) {
return ['ok' => false, 'error' => 'Port out of range'];
}
return ['ok' => true, 'port' => $port];
}
// Alternative: throw — fine in domain layers with try/catch upstream
function parsePortStrict(string $value): int {
$result = parsePort($value);
if (!$result['ok']) {
throw new InvalidArgumentException($result['error']);
}
return $result['port'];
}
JavaScript equivalent
function parsePort(value) {
if (value === '' || value == null) {
return { ok: false, error: 'Port required' };
}
const n = Number(value);
if (Number.isNaN(n)) {
return { ok: false, error: 'Port must be a number' };
}
if (n < 1 || n > 65535) {
return { ok: false, error: 'Port out of range' };
}
return { ok: true, port: n };
}
function applyPort(config, raw) {
const result = parsePort(raw);
if (!result.ok) {
return { ...config, portError: result.error };
}
return { ...config, port: result.port, portError: null };
}
| Approach | Good for |
|---|---|
{ ok, error } return |
Form validation, fetch wrappers, UI branches |
throw new Error(...) |
Unexpected bugs, programmer mistakes |
null with no shape |
Easy to forget checks — avoid in new code |
Caller habit
const result = parsePort(userInput);
if (!result.ok) {
showMessage(result.error);
return;
}
connect(result.port);
Same shape as checking $result['ok'] in PHP before using $result['port'].
Failure mode: mixing styles
Returning { ok: false } sometimes and throwing other times forces callers to handle both paths. Pick a layer convention: error objects at UI boundaries, throws inside domain code that always has a try/catch, for example.
Try it
Use the amber Try it panel below. Click Run.
const inputs = ['', 'abc', '8080', '99999'];
for (const raw of inputs) {
const next = applyPort({ host: 'localhost' }, raw);
console.log(raw, next.port ?? '—', next.portError ?? 'ok');
}
Invalid ports should log an error string; 8080 should show a numeric port and ok.
From PHP to JavaScript — Control Flow, Functions, and Scope