Giter VIP home page Giter VIP logo

p-progress's Introduction

p-progress

Create a promise that reports progress

Useful for reporting progress to the user during long-running async operations.

Install

npm install p-progress

Usage

import pProgress from 'p-progress';

const runJob = async name => pProgress(async progress => {
	const job = new Job(name);

	job.on('data', data => {
		progress(data.length / job.totalSize);
	});

	await job.run();
});

const progressPromise = runJob('Gather rainbows');

progressPromise.onProgress(console.log);
//=> 0.09
//=> 0.23
//=> 0.59
//=> 0.75
//=> 1

await progressPromise;

API

pProgress(function)

Convenience method to make your promise-returning or async function report progress.

The function you specify will be passed the progress() function as a parameter.

instance = new PProgress(executor)

Same as the Promise constructor, but with an appended progress parameter in executor.

PProgress is a subclass of Promise.

progress(percentage)

Type: Function

Call this with progress updates. It expects a number between 0 and 1.

Multiple calls with the same number will result in only one onProgress() event.

Calling with a number lower than previously will be ignored.

Progress percentage 1 is reported for you when the promise resolves. If you set it yourself, it will simply be ignored.

instance.progress

Type: number

The current progress percentage of the promise as a number between 0 and 1.

instance.onProgress(function)

Accepts a function that gets instance.progress as an argument and is called for every progress event.

import {PProgress} from 'p-progress';

const progressPromise = new PProgress((resolve, reject, progress) => {
	const job = new Job();

	job.on('data', data => {
		progress(data.length / job.totalSize);
	});

	job.on('finish', resolve);
	job.on('error', reject);
});

progressPromise.onProgress(progress => {
	console.log(`${progress * 100}%`);
	//=> 9%
	//=> 23%
	//=> 59%
	//=> 75%
	//=> 100%
});

await progressPromise;

PProgress.all(promises, options?)

Convenience method to run multiple promises and get a total progress of all of them. It counts normal promises with progress 0 when pending and progress 1 when resolved. For PProgress type promises, it listens to their onProgress() method for more fine grained progress reporting. You can mix and match normal promises and PProgress promises.

PProgress.allSettled(promises, options?)

Like Promise.allSettled but also exposes the total progress of all of the promises like PProgress.all.

import pProgress, {PProgress} from 'p-progress';
import delay from 'delay';

const progressPromise = () => pProgress(async progress => {
	progress(0.14);
	await delay(52);
	progress(0.37);
	await delay(104);
	progress(0.41);
	await delay(26);
	progress(0.93);
	await delay(55);
	return 1;
});

const progressPromise2 = () => pProgress(async progress => {
	progress(0.14);
	await delay(52);
	progress(0.37);
	await delay(104);
	progress(0.41);
	await delay(26);
	progress(0.93);
	await delay(55);
	throw new Error('Catch me if you can!');
});

const allProgressPromise = PProgress.allSettled([
	progressPromise(),
	progressPromise2()
]);

allProgressPromise.onProgress(console.log);
//=> 0.0925
//=> 0.3425
//=> 0.5925
//=> 0.6025
//=> 0.7325
//=> 0.9825
//=> 1

console.log(await allProgressPromise);
//=> [{status: 'fulfilled', value: 1}, {status: 'rejected', reason: Error: Catch me if you can!}]

promises

Type: Promise[]

An array of promises or promise-returning functions, similar to p-all.

options

Type: object

concurrency

Type: number
Default: Infinity
Minimum: 1

The number of concurrently pending promises.

To run the promises in series, set it to 1.

When this option is set, the first argument must be an array of promise-returning functions.

Related

p-progress's People

Contributors

bendingbender avatar irrelevelephant avatar papb avatar richienb avatar sindresorhus avatar tehshrike avatar thollingshead avatar zikaari avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

p-progress's Issues

the example provided is too vague

Hi,
As a newbie, a real working demo is much better than pseudocode. I literally have no idea how new Job() in the example is implemented.

Rework API to target async/await interfaces

Instead of doing this:

const progressPromise = new PProgress((resolve, reject, progress) => {
	const job = new Job();

	job.on('data', data => {
		progress(data.length / job.totalSize);
	});

	job.on('finish', resolve);
	job.on('error', reject);
});

The API could instead be reworked to do this:

const progressPromise = pProgress(async progress => {
	const job = new Job();

	job.on('data', data => {
		progress(data.length / job.totalSize);
	});

	job.on('error', error => {
		throw error
	});

	return await pEvent(job, 'finish')
})

With the legacy Promise interface working like this:

const progressPromise = pProgress(progress => new Promise((resolve, reject) => {
	const job = new Job();

	job.on('data', data => {
		progress(data.length / job.totalSize);
	});

	job.on('finish', resolve);
	job.on('error', reject);
}))

It looks like PProgress.fn already does something similar but you still have to invoke the returned function:

p-progress/index.js

Lines 36 to 65 in e63f3be

return PProgress.fn(progress => {
const progressMap = new Map();
const iterator = promises[Symbol.iterator]();
const reportProgress = () => {
progress(sum(progressMap) / promises.length);
};
const mapper = async () => {
const next = iterator.next().value;
const promise = typeof next === 'function' ? next() : next;
progressMap.set(promise, 0);
if (promise instanceof PProgress) {
promise.onProgress(percentage => {
progressMap.set(promise, percentage);
reportProgress();
});
}
const value = await promise;
progressMap.set(promise, 1);
reportProgress();
return value;
};
// TODO: This is kinda ugly. Find a better way to do this.
// Maybe `p-map` could accept a number as the first argument?
return pMap(new Array(promises.length), mapper, options);
})();

concurrency doesn't work

I don't see how it is possible to implement concurrency, it says it is possible in options, but practically, consider following example:

import PProgress from 'p-progress'
import delay from 'delay'

const one = async function() {
    console.log("one started.")
    await delay(1000)
    console.log("one finished.")
}
const two = async function () {
    console.log("two started.")
    await delay(1000)
    console.log("two finished.")
}


const x = PProgress.all([
  async function() { console.log("WOW");await one()},
  () => two(),
  delay(3000),
  delay(1000)], {concurrency: 1});

(async function() {
    x.onProgress(console.log)
    console.log(new Date())
    await x
    console.log(new Date())
})()

functions one and two will both not execute at all.

if you put them in array as [one(), two()] then, concurrency will not be honoured, they will execute at the same time.

Wrong PProgress.all type definition

Hi,

I think there is a wrong type definition for PProgress.all method.

PProgress.all should return a PProgress<[AllValuesType]> instead of a PProgress<AllValuesType>

Could you fix it?

Thanks

Better progress API by reporting both done and total values

Current implementation of forcing us to report the progress fraction has a lot of limitations:

  1. Hides the done and total values data from callbacks. This data might be useful in various implementations.
  2. Takes away the ability to handle progress of jobs that don't know about the total value. Some operations just don't know the totalSize, but could still report the progress of how much data has been processed thus far.
  3. Inaccurate progress reporting in PProgress.all(). Without p-progress, or callback consumers knowing the totals, each job is equal no matter how much data it is processing.

I propose not enforcing the passing of fraction, but instead keeping track of both done and total values, reporting them, and letting the user decide how to handle it. This will give us more options and data in our progress implementations.

API:

// current
progress(done / total);
// proposal
progress(done, [total]);

The onProgress callback would than report these values in 1st and 2nd argument. If total is undefined, the end is unknown and user can switch handling:

progressPromise.onProgress((done, total) => {
  if (total === undefined) {
    console.log(`${done} bytes processed thus far`);
  } else {
    console.log(`${(done / total) * 100}% done`);
  }
});

In PProgress.all().onProgress callback, the done and total values in onProgress callback will be the total additions of both of the values of all passes progress promisses. And if any of the promisses is missing the total value, the resulting onProgress callback will also receive undefined as total, since we can no longer tell what the total of all promisses is now.

Huge benefit of knowing about the total total in PProgress.all() callback is a more accurate calculation of the whole progress. For example, if you have 2 jobs, one is processing 1KB, another is processing 99KB, and the 1st ends while the 2nd hasn't even started, you now have an accurate progress of 0.01, instead of 0.5. And if you do want it to report that 0.5, well you just do this in each job progress report:

progress(done / total, 1);

And now each job is equal no matter of the amount of data it's processing.

As you see, this kind of API would give us a lot more power, options, data, and ability to handle jobs with unknown totals in our implementations, while still remaining very simple.

Using `PProgress.fn` `progress()` always returns 1

Example code:

const got = require("got");
const PProgress = require('p-progress');
const { createWriteStream } = require("fs");

const settings = require("../settings.json");

const url = "https://media0.giphy.com/media/4SS0kfzRqfBf2/giphy.gif";
const name = "image.gif";

const fileDownload = PProgress.fn(async (url, name, progress) => {
    const downloadStream = got.stream(url);
    const fileWriterStream = createWriteStream(`${settings.download_target}/${name}`);

    downloadStream
        .on("downloadProgress", ({ transferred, total, percent }) => {
            const percentage = Math.round(percent * 100);
            // console.log(percent);
			// progress(percent);
			// progress(0.01);
            progress(1/10);
            // console.error(`${name} progress: ${transferred}/${total} (${percentage}%)`);
        })
        .on("error", (error) => {
            console.error(`Download failed: ${error.message}`);
            throw new Error(`Download Failed: ${error.message}`);
        });

    fileWriterStream
        .on("error", (error) => {
            console.error(`Could not write file to system: ${error.message}`);
            throw new Error(`File Writing Failed: ${error.message}`);
        })
        .on("finish", () => {
            console.log(`File downloaded to ${name}`);
            return true;
        });

    downloadStream.pipe(fileWriterStream);
});

(async () => {
	const progressPromise = fileDownload(url, 'Gather rainbows');

	progressPromise.onProgress(console.log);

	await progressPromise;
})();

No matter what I've tried it always returns 1 a single time and then doesn't report anything again. Am I missing something?

Node: v14.15.1
Environment: Ubuntu in WSL2 on Windows 10 (application is run in Ubuntu though)

Reported percentage is lower than the last

Recently there were updates somewhere and suddenly one of my CLI's stopped working because p-progress threw an error complaining that the reported percentage is lower than the last reported:

p-progress/index.js

Lines 78 to 86 in a24163f

if (progress === this._progress) {
return;
}
if (progress < this._progress) {
throw new Error('The progress percentage can\'t be lower than the last progress event');
}
this._progress = progress;

I edited p-progress right in node_modules and simply commenting out the throw solves the problem, that case progress < this._progress can be ignored:

throw new Error('The progress percentage can\'t be lower than the last progress event');

Idea

What if we changed it to do nothing if no bigger progress is reported (no matter it's smaller or equal than previous):

if (progress <= this._progress) {
	return;
}

this._progress = progress;

App would just skip unacceptable values until a bigger value is reported. Currently, API seems too strict.

What do you think?

`.onProgress` listeners not added after `.then`/`.catch`

Progress listeners chained after .then or .catch are not being called. Likely an issue with the this context.

Example reproduction:

const PProgress = require('p-progress');
const delay = require('delay');

const createPromise = () => new PProgress(async (resolve, reject, progress) => {
    progress(0.25);
    await delay(500);
    progress(0.75);
    await delay(500);
    resolve();
});

createPromise().onProgress(p => console.log('Bare', p));
createPromise().then(() => {}).onProgress(p => console.log('Then', p));
createPromise().catch(() => {}).onProgress(p => console.log('Catch', p));

// expected
Bare 0.25
Then 0.25
Catch 0.25
Bare 0.75
Then 0.75
Catch 0.75
Bare 1
Then 1
Catch 1

// actual
Bare 0.25
Bare 0.75
Bare 1
Then 1
Catch 1

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.