-
Notifications
You must be signed in to change notification settings - Fork 5
Closed
Labels
Description
Info
difficulty: medium
title: sequence()
type: question
template: javascript
tags: javascriptQuestion
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
- Chain async functions – Accept an array of async functions and execute them sequentially
- Data flow – Pass the result of each function as input to the next function
- Error handling – Stop execution and call final callback when any function returns an error
- Callback interface – Each async function follows the Node.js callback pattern
(error, data) => void - Return async function –
sequence()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);
});
});