Skip to content

sequence() #511

@jsartisan

Description

@jsartisan

Info

difficulty: medium
title: sequence()
type: question
template: javascript
tags: javascript

Question

Implement an async function helper sequence() that chains asynchronous functions together, similar to how pipe() works for synchronous functions. This is a fundamental pattern for handling async operations in sequence.

Requirements

  1. Chain async functions – Accept an array of async functions and execute them sequentially
  2. Data flow – Pass the result of each function as input to the next function
  3. Error handling – Stop execution and call final callback when any function returns an error
  4. Callback interface – Each async function follows the Node.js callback pattern (error, data) => void
  5. Return async functionsequence() should return a new async function that can be called

Key Behaviors

  • Sequential execution – Functions execute one after another, not in parallel
  • Data transformation – Output of each function becomes input of the next
  • Early termination – Stop on first error, don't execute remaining functions
  • Callback propagation – Pass through the callback to the final result
  • Flexible input – Accept any number of async functions in the array

Example

const asyncTimes2 = (callback, num) => {
  setTimeout(() => callback(null, num * 2), 100);
};

const asyncTimes4 = sequence([
  asyncTimes2,
  asyncTimes2
]);

asyncTimes4((error, data) => {
  console.log(data); // 4
}, 1);

// Error handling
const asyncFail = (callback, data) => {
  setTimeout(() => callback(new Error('Something went wrong')), 50);
};

const sequenceWithError = sequence([
  asyncTimes2,
  asyncFail,
  asyncTimes2 // This won't execute
]);

sequenceWithError((error, data) => {
  console.log(error.message); // "Something went wrong"
  console.log(data); // undefined
}, 1);

Key Challenge

The function must handle the sequential execution of callback-based async functions while properly managing data flow and error propagation through the chain.

Template (JavaScript)

javascript.template.md

export function sequence(asyncFuncs) {
  // TODO: Implement me
}
import { sequence } from './index';

describe('sequence', () => {
  beforeEach(() => {
    jest.useFakeTimers();
  });

  afterEach(() => {
    jest.clearAllTimers();
    jest.useRealTimers();
  });

  it('should chain async functions sequentially', (done) => {
    const asyncTimes2 = (callback, num) => {
      setTimeout(() => callback(null, num * 2), 100);
    };

    const asyncTimes4 = sequence([asyncTimes2, asyncTimes2]);

    asyncTimes4((error, data) => {
      expect(error).toBeNull();
      expect(data).toBe(4);
      done();
    }, 1);

    jest.advanceTimersByTime(200);
  });

  it('should handle empty array', (done) => {
    const emptySequence = sequence([]);

    emptySequence((error, data) => {
      expect(error).toBeNull();
      expect(data).toBeUndefined();
      done();
    });
  });

  it('should handle single async function', (done) => {
    const asyncAdd1 = (callback, num) => {
      setTimeout(() => callback(null, num + 1), 100);
    };

    const singleSequence = sequence([asyncAdd1]);

    singleSequence((error, data) => {
      expect(error).toBeNull();
      expect(data).toBe(2);
      done();
    }, 1);

    jest.advanceTimersByTime(100);
  });

  it('should stop on first error', (done) => {
    const asyncTimes2 = (callback, num) => {
      setTimeout(() => callback(null, num * 2), 100);
    };

    const asyncFail = (callback, data) => {
      setTimeout(() => callback(new Error('Something went wrong')), 50);
    };

    const asyncNeverCalled = jest.fn((callback, data) => {
      setTimeout(() => callback(null, data + 1), 100);
    });

    const sequenceWithError = sequence([
      asyncTimes2,
      asyncFail,
      asyncNeverCalled
    ]);

    sequenceWithError((error, data) => {
      expect(error.message).toBe('Something went wrong');
      expect(data).toBeUndefined();
      expect(asyncNeverCalled).not.toHaveBeenCalled();
      done();
    }, 1);

    jest.advanceTimersByTime(200);
  });

  it('should handle multiple chained operations', (done) => {
    const asyncAdd1 = (callback, num) => {
      setTimeout(() => callback(null, num + 1), 50);
    };

    const asyncTimes3 = (callback, num) => {
      setTimeout(() => callback(null, num * 3), 50);
    };

    const asyncSubtract2 = (callback, num) => {
      setTimeout(() => callback(null, num - 2), 50);
    };

    const complexSequence = sequence([
      asyncAdd1,    // 1 -> 2
      asyncTimes3,  // 2 -> 6
      asyncSubtract2 // 6 -> 4
    ]);

    complexSequence((error, data) => {
      expect(error).toBeNull();
      expect(data).toBe(4);
      done();
    }, 1);

    jest.advanceTimersByTime(150);
  });

  it('should handle async functions with different timing', (done) => {
    const fastAsync = (callback, data) => {
      setTimeout(() => callback(null, data + 1), 10);
    };

    const slowAsync = (callback, data) => {
      setTimeout(() => callback(null, data * 2), 100);
    };

    const mixedSequence = sequence([fastAsync, slowAsync]);

    mixedSequence((error, data) => {
      expect(error).toBeNull();
      expect(data).toBe(4); // (1 + 1) * 2
      done();
    }, 1);

    jest.advanceTimersByTime(200);
  });

  it('should handle string data transformation', (done) => {
    const asyncToUpper = (callback, str) => {
      setTimeout(() => callback(null, str.toUpperCase()), 50);
    };

    const asyncAddExclamation = (callback, str) => {
      setTimeout(() => callback(null, str + '!'), 50);
    };

    const stringSequence = sequence([asyncToUpper, asyncAddExclamation]);

    stringSequence((error, data) => {
      expect(error).toBeNull();
      expect(data).toBe('HELLO!');
      done();
    }, 'hello');

    jest.advanceTimersByTime(100);
  });

  it('should handle error in first function', (done) => {
    const asyncFail = (callback, data) => {
      setTimeout(() => callback(new Error('First error')), 50);
    };

    const asyncNeverCalled = jest.fn((callback, data) => {
      setTimeout(() => callback(null, data), 100);
    });

    const failingSequence = sequence([asyncFail, asyncNeverCalled]);

    failingSequence((error, data) => {
      expect(error.message).toBe('First error');
      expect(data).toBeUndefined();
      expect(asyncNeverCalled).not.toHaveBeenCalled();
      done();
    }, 'test');

    jest.advanceTimersByTime(100);
  });
});

Template (TypeScript)

typescript.template.md

type Callback = (error: Error | null, data: any) => void;
type AsyncFunc = (callback: Callback, data: any) => void;

export function sequence(asyncFuncs: AsyncFunc[]): AsyncFunc {
  // TODO: Implement me
}
import { sequence } from './index';

describe('sequence', () => {
  beforeEach(() => {
    jest.useFakeTimers();
  });

  afterEach(() => {
    jest.clearAllTimers();
    jest.useRealTimers();
  });

  it('should chain async functions sequentially', (done) => {
    const asyncTimes2 = (callback: any, num: number) => {
      setTimeout(() => callback(null, num * 2), 100);
    };

    const asyncTimes4 = sequence([asyncTimes2, asyncTimes2]);

    asyncTimes4((error, data) => {
      expect(error).toBeNull();
      expect(data).toBe(4);
      done();
    }, 1);

    jest.advanceTimersByTime(200);
  });

  it('should handle empty array', (done) => {
    const emptySequence = sequence([]);

    emptySequence((error, data) => {
      expect(error).toBeNull();
      expect(data).toBeUndefined();
      done();
    });
  });

  it('should handle single async function', (done) => {
    const asyncAdd1 = (callback: any, num: number) => {
      setTimeout(() => callback(null, num + 1), 100);
    };

    const singleSequence = sequence([asyncAdd1]);

    singleSequence((error, data) => {
      expect(error).toBeNull();
      expect(data).toBe(2);
      done();
    }, 1);

    jest.advanceTimersByTime(100);
  });

  it('should stop on first error', (done) => {
    const asyncTimes2 = (callback: any, num: number) => {
      setTimeout(() => callback(null, num * 2), 100);
    };

    const asyncFail = (callback: any, data: any) => {
      setTimeout(() => callback(new Error('Something went wrong')), 50);
    };

    const asyncNeverCalled = jest.fn((callback: any, data: any) => {
      setTimeout(() => callback(null, data + 1), 100);
    });

    const sequenceWithError = sequence([
      asyncTimes2,
      asyncFail,
      asyncNeverCalled
    ]);

    sequenceWithError((error, data) => {
      expect(error.message).toBe('Something went wrong');
      expect(data).toBeUndefined();
      expect(asyncNeverCalled).not.toHaveBeenCalled();
      done();
    }, 1);

    jest.advanceTimersByTime(200);
  });

  it('should handle multiple chained operations', (done) => {
    const asyncAdd1 = (callback: any, num: number) => {
      setTimeout(() => callback(null, num + 1), 50);
    };

    const asyncTimes3 = (callback: any, num: number) => {
      setTimeout(() => callback(null, num * 3), 50);
    };

    const asyncSubtract2 = (callback: any, num: number) => {
      setTimeout(() => callback(null, num - 2), 50);
    };

    const complexSequence = sequence([
      asyncAdd1,    // 1 -> 2
      asyncTimes3,  // 2 -> 6
      asyncSubtract2 // 6 -> 4
    ]);

    complexSequence((error, data) => {
      expect(error).toBeNull();
      expect(data).toBe(4);
      done();
    }, 1);

    jest.advanceTimersByTime(150);
  });

  it('should handle async functions with different timing', (done) => {
    const fastAsync = (callback: any, data: number) => {
      setTimeout(() => callback(null, data + 1), 10);
    };

    const slowAsync = (callback: any, data: number) => {
      setTimeout(() => callback(null, data * 2), 100);
    };

    const mixedSequence = sequence([fastAsync, slowAsync]);

    mixedSequence((error, data) => {
      expect(error).toBeNull();
      expect(data).toBe(4); // (1 + 1) * 2
      done();
    }, 1);

    jest.advanceTimersByTime(200);
  });

  it('should handle string data transformation', (done) => {
    const asyncToUpper = (callback: any, str: string) => {
      setTimeout(() => callback(null, str.toUpperCase()), 50);
    };

    const asyncAddExclamation = (callback: any, str: string) => {
      setTimeout(() => callback(null, str + '!'), 50);
    };

    const stringSequence = sequence([asyncToUpper, asyncAddExclamation]);

    stringSequence((error, data) => {
      expect(error).toBeNull();
      expect(data).toBe('HELLO!');
      done();
    }, 'hello');

    jest.advanceTimersByTime(100);
  });

  it('should handle error in first function', (done) => {
    const asyncFail = (callback: any, data: any) => {
      setTimeout(() => callback(new Error('First error')), 50);
    };

    const asyncNeverCalled = jest.fn((callback: any, data: any) => {
      setTimeout(() => callback(null, data), 100);
    });

    const failingSequence = sequence([asyncFail, asyncNeverCalled]);

    failingSequence((error, data) => {
      expect(error.message).toBe('First error');
      expect(data).toBeUndefined();
      expect(asyncNeverCalled).not.toHaveBeenCalled();
      done();
    }, 'test');

    jest.advanceTimersByTime(100);
  });
});

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions