The FIFO read and write ports can be on different clocks. For systems with asynchronous clocks this solves the basic cross-domain data transfer problem, so long as the FIFO read and write pointers don't cross.
Even if the clocks are the same frequency, isolating large areas in separate timing domains using FIFOs or register slices (that is, shallow FIFOs) eases system timing closure, at the expense of latency. This is especially useful in large SoCs where maintaining clock phase alignment across a large die becomes very difficult, even with careful clock tree design.
For those FIFO boundary cases where flow control comes up (full, almost full, empty, etc), flag generation resolves the clock domain crossing by using special techniques, such as gray-coding, to ensure reliable full/empty calculations within each clock domain.
This cross-boundary flag synchronization adds latency however, so the designer must allocate extra FIFO room to guard against overrun. This can be by using an almost-full indicator, or by adding small FIFO called a skid buffer that catches the extra data emitted by the host during the flag latency time.