Skip to the code
cf.

Before and after

MediumFunctions

Write two functions that decide whether to call fn by counting the calls to the function they return.

before(n, fn) returns a function that calls fn while it has been called fewer than n times, so for the first n - 1 calls. From the nth call on, it stops calling fn and returns the result of the last call that did, or undefined if none did.

after(n, fn) returns a function that does nothing and returns undefined for the first n - 1 calls, then calls fn on the nth call and on every call after it.

Whenever fn runs, it gets the caller's this and arguments, and its result is returned. Every returned function counts its own calls.

const twice = before(3, (x) => x * 2);
twice(1); // 2
twice(2); // 4
twice(3); // 4: fn isn't called any more
 
const done = after(3, () => "all loaded");
done(); // undefined
done(); // undefined
done(); // "all loaded"
done(); // "all loaded"

What the tests check

  1. before calls fn only for the first n - 1 calls
    const fn = spy();
    const limited = before(3, fn);
    limited();
    limited();
    expect(fn).toHaveBeenCalledTimes(2);
    limited();
    limited();
    expect(fn).toHaveBeenCalledTimes(2);
    
  2. before keeps returning the last result once it stops
    const double = before(3, (x) => x * 2);
    expect(double(1)).toBe(2);
    expect(double(2)).toBe(4);
    expect(double(3)).toBe(4);
    expect(double(10)).toBe(4);
    
  3. before(1, fn) never calls fn
    const fn = spy(() => "ran");
    const never = before(1, fn);
    expect(never()).toBeUndefined();
    expect(never()).toBeUndefined();
    expect(fn).not.toHaveBeenCalled();
    
  4. after ignores the first n - 1 calls
    const fn = spy();
    const done = after(3, fn);
    done();
    done();
    expect(fn).not.toHaveBeenCalled();
    done();
    expect(fn).toHaveBeenCalledTimes(1);
    done();
    expect(fn).toHaveBeenCalledTimes(2);
    
  5. after returns undefined until it calls fn, then fn's result
    const ready = after(2, (x) => x + 1);
    expect(ready(1)).toBeUndefined();
    expect(ready(5)).toBe(6);
    expect(ready(7)).toBe(8);
    
  6. after(1, fn) calls fn every time
    const fn = spy();
    const always = after(1, fn);
    always();
    always();
    expect(fn).toHaveBeenCalledTimes(2);
    
  7. both pass on the caller's this and arguments
    const early = spy();
    const late = spy();
    const obj = { early: before(2, early), late: after(1, late) };
    obj.early("a", 1);
    obj.late("b", 2);
    expect(early.calls[0].this).toBe(obj);
    expect(early).toHaveBeenCalledWith("a", 1);
    expect(late.calls[0].this).toBe(obj);
    expect(late).toHaveBeenCalledWith("b", 2);
    
  8. each returned function counts its own calls
    const fn = spy();
    const first = after(2, fn);
    const second = after(2, fn);
    first();
    second();
    expect(fn).not.toHaveBeenCalled();
    first();
    expect(fn).toHaveBeenCalledTimes(1);
     
    const other = spy();
    const one = before(2, other);
    const two = before(2, other);
    one();
    two();
    expect(other).toHaveBeenCalledTimes(2);
    

Learn more

solution.jsSaved in this browser

Run tests runs every test on your code, here in your browser. The problem is solved when they all pass.

Ctrl + Enter runs the tests (⌘ + Enter on a Mac).