Open heikomat opened 7 years ago
Ein Beispiel:
function delay(milliseconds: number, count: number): Promise<number> {
return new Promise<number>(resolve => {
setTimeout(() => {
resolve(count);
}, milliseconds);
});
}
// async function always return a Promise
async function dramaticWelcome(): Promise<void> {
console.log("Hello");
for (let i = 0; i < 5; i++) {
// await is converting Promise<number> into number
const count:number = await delay(500, i);
console.log(count);
}
console.log("World!");
}
dramaticWelcome();
Das Transpilat dazu (es5 target):
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = y[op[0] & 2 ? "return" : op[0] ? "throw" : "next"]) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [0, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
function delay(milliseconds, count) {
return new Promise(function (resolve) {
setTimeout(function () {
resolve(count);
}, milliseconds);
});
}
// async function always return a Promise
function dramaticWelcome() {
return __awaiter(this, void 0, void 0, function () {
var i, count;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
console.log("Hello");
i = 0;
_a.label = 1;
case 1:
if (!(i < 5)) return [3 /*break*/, 4];
return [4 /*yield*/, delay(500, i)];
case 2:
count = _a.sent();
console.log(count);
_a.label = 3;
case 3:
i++;
return [3 /*break*/, 1];
case 4:
console.log("World!");
return [2 /*return*/];
}
});
});
}
dramaticWelcome();
Are you sure about this?
Ich persönlich finde die Lösung private Methoden mit nem Unterstrich zu bennen unschön und es ist für mich ein notwendiges Übel da JavaScript keine privaten Methoden erlaubt. Mich stört es im Lesefluss.
In TypeScript gibt es die Möglichkeit private Methoden zu definieren, und auch wenn man in TypeScript JavaScript verwenden kann, ist es für mich eigentlich kein Argument einen Workaround zu verwenden der nur auf Grund von fehlenden Features in JavaScript benutzt wird.
Das man irgendwann von TypeSript auf JavaScript wechseln will und dafür die transpilierten Sourcen nutzen will halte ich für Fragwürdig. Ich kann mir gut vorstellen das TypeScript einigen Code sehr unübersichtlich generiert. Kannst du da was genaueres zu sagen @heikomat ?
Das Argument mit den abgeleiteten Klassen zwischen TypeScript in JavaScript kann ich so noch nicht ganz nachvollziehen, in TypeScript Projekten sollte man IMHO plain JavaScript vermeiden so gut wie es geht. @5ebastianMeier
Bei Sprachen ohne private/public Modifikator: Ja In anderen Fällen ein klares: Nein
@NullEnt1ty @lmoe ich persönlich bin auch kein Fan vom underscore, aber wenn man nicht grade ES5 als ziel angibt, und die tsconfig richtig einstellt, kann ter typescript-compiler auch relativ sauberen JS-Code erzeugen
Wenn man sich irgendwann entscheiden sollte, TypeScript nicht mehr zu nehmen, und wieder auf JavaScript umzusteigen, und man dafür in einem ehemaligen TS-Projekt das JS-kompilat als neue sourcecode-grundlage verwenden möchte, wären hier die Klassen-member bereits korrekt benannt
Ich kann dem TypeScript Compiler doch auch bestimmt sagen, dass er private Properties entsprechend mit einem Underscore präfixen soll.
Ich kann dem TypeScript Compiler doch auch bestimmt sagen, dass er private Properties entsprechend mit einem Underscore präfixen soll.
Ich bin mir nicht sicher, ob das geht. Mächtig genug wäre der TSC, ich weiß nur nicht, obs implementiert ist. Wenn das allerdings geht, dann ist das auf-JS-umsteigen-Argument damit hinfällig
aber wenn man nicht grade ES5 als ziel angibt, und die tsconfig richtig einstellt, kann ter typescript-compiler auch relativ sauberen JS-Code erzeugen
Naja, in ES6 sieht der Code auch nicht viel schöner aus:
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
function delay(milliseconds, count) {
return new Promise(resolve => {
setTimeout(() => {
resolve(count);
}, milliseconds);
});
}
// async function always return a Promise
function dramaticWelcome() {
return __awaiter(this, void 0, void 0, function* () {
console.log("Hello");
for (let i = 0; i < 5; i++) {
// await is converting Promise<number> into number
const count = yield delay(500, i);
console.log(count);
}
console.log("World!");
});
}
dramaticWelcome();
Ich habe oft den Underscore-Prefix in zusammenhang mit Gettern/Settern gesehen, was passiert damit?
class Fuu {
private _bar: string;
public get bar() {
return this._bar;
}
}
@Paulomart hier würde der underscore nur zum Umgehen von Namenskonflikten dienen, nicht zur Darstellung ob etwas private oder public ist. Da könnte man einfach einen anderen Namen verwenden.
z.B. könnte man für den member ohne getter/setter einen technischen Namen verwenden, der beschreibt was es ist, und für den member mit getter/setter einen fachlichen, der beschreibt was sein Zweck ist.
Wenn einem damit keine unterschiedlichen Namen einfallen, könnte man sich immernoch eine Namingconvention ausdenken. Das könnte auch darauf hinaus laufen, dass man sagt: fällt dir kein besserer Name ein, mach einen unterstrich vor dem member ohne getter. Aber das wäre dann eine Fachlich andere sache, weil der unterstrich dann nicht zum darstellen der Zugriffsmöglichkeiten dient, sondern zum umgehen von Namenskonflikten
Ich kann dem TypeScript Compiler doch auch bestimmt sagen, dass er private Properties entsprechend mit einem Underscore präfixen soll.
Gibts sehr sicher nicht und wäre dagegen, wenn es das gäbe. Klingt sehr fehleranfällig. Gegeben sei folgender Fall:
private var_1;
private var_2;
public function getVar(index: number) {
return this[`var_${index}`];
}
Wenn tsc aus var_1
zur Laufzeit einfach _var_1
macht, deckt sich das nicht mit dem return-value. Denn wie soll TypeScript wissen, dass es sich bei meinem konstruierten Zugriff um eine private Variable handelt?
Würd deshalb auch nicht versuchen mit der API von TypeScript selber so einen Konverter zu bauen.
Wäre auch für Verzicht von _
.
Eine Randnotiz: Es gibt ein ES-Proposal für richtige private class-member(stage 3, könnte also gut durchkommen), wo man #
anstelle von _
verwendet.
Nicht sicher, wie typescript da adaptiert, aber dann hätten wir private
, #...
, und legacy-Code mit _...
. Kein starkes Argument, weils nur Proposal ist.
@Paulomart: In deinem Beispiel sollte das nicht nötig sein oder? Ich kenne TS nicht wirklich, aber Underscore und this.
schaut doppelt aus. Eins sollte zur Unterscheidung ausreichen.
@heikomat: In .NET ist ein Backing-Field für eine Property ein technisches Hilfsmittel; in TS nicht? Eigentlich suggeriert eine Property, dass Getter/Setter nichts weiter machen, als etwas zu lesen oder zu setzen. Damit ist es die gleiche Fachlichkeit - ich wüsste nicht, wieso das Feld dann anders heißen sollte, als die Property?
@LeoTT: Mh, macht ihr das so? Dann hoff ich, dass das Proposal für das nameof()
-Feature bald durch ist. Es ist schon ein sehr brüchiges Vorgehen, auf Member mit Magic Strings zugreifen zu müssen.
Der Underscore wird in typisierten Sprachen genauso verwendet. In C# wird es häufig als Alternative zu this.
gesehen. Microsoft selbst verwendet den Underscore in TS und .NET Core - gibt aber bewusst keine Empfehlung heraus.
Pers. Meinung:
In .NET greift man mit this.
prinzipiell auf jeden nicht-statischen Member (Methode, Property, Field) außer Parametern zu, also hat man da bzgl. Lesbarkeit nicht viel gewonnen. Besonders, weil wie Heiko schon sagte, man in solchen Fällen einfach passendere Namen wählen kann. Ich finde jedoch das Argument schrecklich, es würde späteres Refactoring vereinfachen (Gruß an Private Properties).
@wolf-5minds in JavaScript/TypeScript kann man getter und setter methoden verwenden, um einen member zu definieren. Diese beiden Beispiele machen haar genau das gleiche (siehe JS getter und setter:
class SomeClass {
someMember: string = 'test';
constructor() {
console.log(this.someMember);
}
}
class SomeClass {
get someMember(): string {
return 'test';
}
constructor() {
console.log(this.someMember);
}
}
Der Unterschied in der Verwendung ist, dass uns getter und setter-methoden Kontrolle darüber geben, was genau passieren soll, wenn ein member gesetzt oder gelesen wird. Zum Beispiel könnten man folgendes tun, was ohne die getter methode nur mit umwegen funktioniert:
class SomeClass {
get dbStatus(): string {
if (this.dbIsConnected) {
return 'connected';
}
return 'not connected';
}
constructor() {
console.log(this.dbStatus);
}
}
Hier greife ich weiterhin auf den member zu, wie auf jeden anderen normalen member auch, aber jedes mal wenn ich das tue wird etwas logik ausgeführt, was ggf auch ändert, was in diesem Fall geloggt wird.
Zudem hat man bei JS/TS kein implizites this
! Wenn du auf einen member der eigenen Klasse zugreifen willst musst du this
verwenden
Bisher haben wir häufig eine kombi aus
verwendet, damit wir, falls wir irgendwann mal den Rückgabewert für diese Variable ändern möchten, wir in getter/setter an einem zentralen Ort bestimmen können, wie die variable sich verhält. andernfalls kann die variable irgendwo irgendwie gesetzt, und irgendwo anders später ausgelesen werden, und wer weiß was beinhalten. Mit getter und setter können wir immer am deklarations-ort des members bestimmen, was er sich verhält und was er beinhaltet.
In der Praxis läuft das allerdings häufig darauf hinaus, dass wir an vielen Stellen konstrukte haben wie das, was @Paulomart dargestellt hat: eine underscore-variable, und getter/setter die nur darauf arbeiten, und nichts weiter tun.
Ich hoffe damit ist etwas besser verständlich, wie sich getter/setter in JS/TS verhalten, und wo sie sich evtl von anderen Sprachen unterscheiden.
Ah, Danke @heikomat. Ja, es hat zum Verständnis beigetragen. Ich wollte auch nur auf ein paar Argumente eingehen, nicht die Diskussion hijacken.
Ich finde den Underscore auch eher störend, als hilfreich. Ich kann verstehen, dass bei Interfaces das I
-Präfix in Code Reviews nützlich ist, allerdings sehe ich diesen Vorteil bei Access Modifiers nicht.
Wenn ich eine Code Review mache, achte ich eher selten darauf, ob nun an dieser einen Stelle auf eine private
oder public
Variable zugegriffen wird. Ich wüsste auch spontan nicht, warum ich darauf achten müsste (Ausnahme wäre hier die Deklaration). Wenn der Programmierer auf eine private Variable außerhalb der Klasse zugreift, wird ihm die statische Code Analyse schon auf die Finger hauen.
Nach dem gleichen Argument, könnte man auch die ungarische Notation wiedereinführen...
Warum existiert der underscore-präfix: JavaScript Entwickler gehen aufgrund fehlender zugriffs-scopes idr. her, und versehen Variablen, die nur für die interne Verwendung gedacht sind mit einem underscore-präfix. Damit wird anderen, die den code lesen/verwenden signalisiert: "Ich weiß das ist JS, und ich kann dich nicht dran hindern, dass zu nutzen, aber lass es besser sein, das ist nicht für die externe Nutzung gedacht".
Wie handlen andere typisierte Sprachen das: Bei typisierten Sprachen tritt das Problem nicht auf, da man hier über private/public/protected einstellen kann, wer wie zugriff haben soll.
Warum TypeScript ein Sonderfall ist: Jetzt haben wir allerdings TypeScript: Ein JavaScript-Superset, welches aber ziemlich typisiert ist, und Zugriffsmodifikatoren unterstützt. Da wir uns alle einig sind, dass wir zumindest konsequent entweder komplett mit, oder ganz ohne underscore arbeiten sollten ist hier das Issue, indem wir uns auf eins von beidem einigen können.
Hier noch die pro- und contra-argumente, die mir spontan einfallen bzw. die ich kürzlich gehört habe:
pro underscore-präfix:
contra underscore-präfix:
Ich kann leider nur 10 Leute assignen, deshalb hier ein paar mentions von Personen, von denen ich glaube, dass sie etwas beitragen können/möchten, und die nicht assigned sind: @LeoTT @Paulomart @Vyperus