docs: def map_batch_parallel(input_list, batch_size, item_mapper=None, batch_mapper=None, flatten=True, n_jobs=-1, **kwargs):
      """
      Split the data into batches and process each batch in its own thread.
  
      Args:
          input_list: An input object that has a list-like interface (indexing and slicing).
          item_mapper: (optional) A function to apply to each item in the batch.
          batch_mapper: (optional) A function to apply to each batch. Either item_mapper or batch_mapper must be set.
          flatten: Whether to unwrap individual batch results or keep them grouped by batch.
          n_jobs: The number of parallel processing jobs. -1 will use the number of CPUs on the system.
          batch_size: The maximum number of input items in each batch. -1 will store all data as a single batch.
          **kwargs: Additional keyword arguments to joblib.Parallel.
  
      Returns:
          A list representing the combined output from the mapper function called on all input items of each batch.
      """
  
      # We must specify either how to process each batch or how to process each item.
      if item_mapper is None and batch_mapper is None:
          raise ValueError('You should specify either batch_mapper or item_mapper.')
  
      if batch_mapper is None:
          batch_mapper = _default_batch_mapper
  
      batches = split_into_batches(input_list, batch_size, batch_storage_dir='')
      all_batch_results = Parallel(n_jobs=n_jobs, **kwargs)(
          delayed(batch_mapper)(batch['data'], item_mapper)
          for batch in progressbar(
              batches,
              desc='Batches',
              total=len(batches),
              file=sys.stdout,
          )
      )
  
      # Unwrap the individual batch results if necessary.
      if flatten:
          final_result = []
          for batch_result in all_batch_results:
              final_result.extend(batch_result)
      else:
          final_result = all_batch_results
  
      return final_result