The keywords are the same and the execution model is not. A JavaScript promise starts running when it is created; a C# Task from an async method starts when it is created too, but the thread it resumes on is a decision the framework makes.
// fine in ASP.NET Core, deadlocks in older ASP.NET
var order = _orders.FindAsync(id).Result; // never do this
// what to write
var order = await _orders.FindAsync(id);
// and the one that catches people: this runs sequentially
foreach (var id in ids)
{
results.Add(await _orders.FindAsync(id));
}
// this does not
var results = await Task.WhenAll(ids.Select(id => _orders.FindAsync(id)));
.Result and .Wait() block the thread waiting for a task that may need that thread to complete, which is the classic deadlock — ASP.NET Core removed the synchronisation context that caused it, so the failure is now a thread-pool starvation under load rather than an immediate hang, which is harder to diagnose. The sequential loop is the same mistake anyone makes with await in JavaScript, and Task.WhenAll is the same fix as Promise.all. Async has to go all the way up the call stack, which is why a synchronous PHP mindset produces code that compiles and performs worse than not using it.