UNPKG

seventh

Version:

A lean Promises and Async lib for ES6/ES7

622 lines (475 loc) 13.4 kB
/* Seventh Copyright (c) 2017 - 2020 Cédric Ronvel The MIT License (MIT) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ "use strict" ; const Promise = require( './seventh.js' ) ; // This object is used as a special unique value for array hole (see Promise.filter()) const HOLE = {} ; function noop() {} Promise.all = ( iterable ) => { var index = - 1 , settled = false , count = 0 , length = Infinity , value , values = [] , allPromise = new Promise() ; for ( value of iterable ) { if ( settled ) { break ; } const promiseIndex = ++ index ; Promise.resolve( value ) .then( value_ => { if ( settled ) { return ; } values[ promiseIndex ] = value_ ; count ++ ; if ( count >= length ) { settled = true ; allPromise._resolveValue( values ) ; } } , error => { if ( settled ) { return ; } settled = true ; allPromise.reject( error ) ; } ) ; } length = index + 1 ; if ( ! length ) { allPromise._resolveValue( values ) ; } return allPromise ; } ; // Maybe faster, but can't find any reasonable grounds for that ATM //Promise.all = Promise._allArray = ( iterable ) => { var length = iterable.length ; if ( ! length ) { Promise._resolveValue( [] ) ; } var index , runtime = { settled: false , count: 0 , length: length , values: [] , allPromise: new Promise() } ; for ( index = 0 ; ! runtime.settled && index < length ; index ++ ) { Promise._allArrayOne( iterable[ index ] , index , runtime ) ; } return runtime.allPromise ; } ; // internal for allArray Promise._allArrayOne = ( value , index , runtime ) => { Promise._bareThen( value , value_ => { if ( runtime.settled ) { return ; } runtime.values[ index ] = value_ ; runtime.count ++ ; if ( runtime.count >= runtime.length ) { runtime.settled = true ; runtime.allPromise._resolveValue( runtime.values ) ; } } , error => { if ( runtime.settled ) { return ; } runtime.settled = true ; runtime.allPromise.reject( error ) ; } ) ; } ; Promise.allSettled = ( iterable ) => { var index = - 1 , settled = false , count = 0 , length = Infinity , value , values = [] , allPromise = new Promise() ; for ( value of iterable ) { if ( settled ) { break ; } const promiseIndex = ++ index ; Promise.resolve( value ) .then( value_ => { if ( settled ) { return ; } values[ promiseIndex ] = { status: 'fulfilled' , value: value_ } ; count ++ ; if ( count >= length ) { settled = true ; allPromise._resolveValue( values ) ; } } , error => { if ( settled ) { return ; } values[ promiseIndex ] = { status: 'rejected' , reason: error } ; count ++ ; if ( count >= length ) { settled = true ; allPromise._resolveValue( values ) ; } } ) ; } length = index + 1 ; if ( ! length ) { allPromise._resolveValue( values ) ; } return allPromise ; } ; // Promise.all() with an iterator Promise.every = Promise.map = ( iterable , iterator ) => { var index = - 1 , settled = false , count = 0 , length = Infinity , value , values = [] , allPromise = new Promise() ; for ( value of iterable ) { if ( settled ) { break ; } const promiseIndex = ++ index ; Promise.resolve( value ) .then( value_ => { if ( settled ) { return ; } return iterator( value_ , promiseIndex ) ; } ) .then( value_ => { if ( settled ) { return ; } values[ promiseIndex ] = value_ ; count ++ ; if ( count >= length ) { settled = true ; allPromise._resolveValue( values ) ; } } , error => { if ( settled ) { return ; } settled = true ; allPromise.reject( error ) ; } ) ; } length = index + 1 ; if ( ! length ) { allPromise._resolveValue( values ) ; } return allPromise ; } ; /* It works symmetrically with Promise.all(), the resolve and reject logic are switched. Therefore, it resolves to the first resolving promise OR reject if all promises are rejected with, as a reason an AggregateError of all promise rejection reasons. */ Promise.any = ( iterable ) => { var index = - 1 , settled = false , count = 0 , length = Infinity , value , errors = [] , anyPromise = new Promise() ; for ( value of iterable ) { if ( settled ) { break ; } const promiseIndex = ++ index ; Promise.resolve( value ) .then( value_ => { if ( settled ) { return ; } settled = true ; anyPromise._resolveValue( value_ ) ; } , error => { if ( settled ) { return ; } errors[ promiseIndex ] = error ; count ++ ; if ( count >= length ) { settled = true ; anyPromise.reject( new AggregateError( errors ) , 'Promise.any(): All promises have rejected' ) ; } } ) ; } length = index + 1 ; if ( ! length ) { anyPromise.reject( new RangeError( 'Promise.any(): empty array' ) ) ; } return anyPromise ; } ; // Like Promise.any() but with an iterator Promise.some = ( iterable , iterator ) => { var index = - 1 , settled = false , count = 0 , length = Infinity , value , errors = [] , anyPromise = new Promise() ; for ( value of iterable ) { if ( settled ) { break ; } const promiseIndex = ++ index ; Promise.resolve( value ) .then( value_ => { if ( settled ) { return ; } return iterator( value_ , promiseIndex ) ; } ) .then( value_ => { if ( settled ) { return ; } settled = true ; anyPromise._resolveValue( value_ ) ; } , error => { if ( settled ) { return ; } errors[ promiseIndex ] = error ; count ++ ; if ( count >= length ) { settled = true ; anyPromise.reject( new AggregateError( errors , 'Promise.some(): All promises have rejected' ) ) ; } } ) ; } length = index + 1 ; if ( ! length ) { anyPromise.reject( new RangeError( 'Promise.some(): empty array' ) ) ; } return anyPromise ; } ; /* More close to Array#filter(). The iterator should return truthy if the array element should be kept, or falsy if the element should be filtered out. Any rejection reject the whole promise. */ Promise.filter = ( iterable , iterator ) => { var index = - 1 , settled = false , count = 0 , length = Infinity , value , values = [] , filterPromise = new Promise() ; for ( value of iterable ) { if ( settled ) { break ; } const promiseIndex = ++ index ; Promise.resolve( value ) .then( value_ => { if ( settled ) { return ; } values[ promiseIndex ] = value_ ; return iterator( value_ , promiseIndex ) ; } ) .then( iteratorValue => { if ( settled ) { return ; } count ++ ; if ( ! iteratorValue ) { values[ promiseIndex ] = HOLE ; } if ( count >= length ) { settled = true ; values = values.filter( e => e !== HOLE ) ; filterPromise._resolveValue( values ) ; } } , error => { if ( settled ) { return ; } settled = true ; filterPromise.reject( error ) ; } ) ; } length = index + 1 ; if ( ! length ) { filterPromise._resolveValue( values ) ; } else if ( count >= length ) { settled = true ; values = values.filter( e => e !== HOLE ) ; filterPromise._resolveValue( values ) ; } return filterPromise ; } ; // forEach performs reduce as well, if a third argument is supplied // Force a function statement because we are using arguments.length, so we can support accumulator equals to undefined Promise.foreach = Promise.forEach = function( iterable , iterator , accumulator ) { var index = - 1 , isReduce = arguments.length >= 3 , it = iterable[Symbol.iterator]() , forEachPromise = new Promise() , lastPromise = Promise.resolve( accumulator ) ; // The array-like may contains promises that could be rejected before being handled if ( Promise.warnUnhandledRejection ) { Promise._handleAll( iterable ) ; } var nextElement = () => { lastPromise.then( accumulator_ => { let { value , done } = it.next() ; index ++ ; if ( done ) { forEachPromise.resolve( accumulator_ ) ; } else { lastPromise = Promise.resolve( value ).then( isReduce ? value_ => iterator( accumulator_ , value_ , index ) : value_ => iterator( value_ , index ) ) ; nextElement() ; } } , error => { forEachPromise.reject( error ) ; // We have to eat all remaining promise errors for ( ;; ) { let { value , done } = it.next() ; if ( done ) { break ; } //if ( ( value instanceof Promise ) || ( value instanceof NativePromise ) ) if ( Promise.isThenable( value ) ) { value.then( noop , noop ) ; } } } ) ; } ; nextElement() ; return forEachPromise ; } ; Promise.reduce = ( iterable , iterator , accumulator ) => { // Force 3 arguments return Promise.forEach( iterable , iterator , accumulator ) ; } ; /* Same than map, but iterate over an object and produce an object. Think of it as a kind of Object#map() (which of course does not exist). */ Promise.mapObject = ( inputObject , iterator ) => { var settled = false , count = 0 , keys = Object.keys( inputObject ) , length = keys.length , outputObject = {} , mapPromise = new Promise() ; for ( let i = 0 ; ! settled && i < length ; i ++ ) { const key = keys[ i ] ; const value = inputObject[ key ] ; Promise.resolve( value ) .then( value_ => { if ( settled ) { return ; } return iterator( value_ , key ) ; } ) .then( value_ => { if ( settled ) { return ; } outputObject[ key ] = value_ ; count ++ ; if ( count >= length ) { settled = true ; mapPromise._resolveValue( outputObject ) ; } } , error => { if ( settled ) { return ; } settled = true ; mapPromise.reject( error ) ; } ) ; } if ( ! length ) { mapPromise._resolveValue( outputObject ) ; } return mapPromise ; } ; // Like map, but with a concurrency limit Promise.concurrent = ( limit , iterable , iterator ) => { var index = - 1 , settled = false , running = 0 , count = 0 , length = Infinity , value , done = false , values = [] , it = iterable[Symbol.iterator]() , concurrentPromise = new Promise() ; // The array-like may contains promises that could be rejected before being handled if ( Promise.warnUnhandledRejection ) { Promise._handleAll( iterable ) ; } limit = + limit || 1 ; const runBatch = () => { while ( ! done && running < limit ) { //console.log( "Pre" , index ) ; ( { value , done } = it.next() ) ; if ( done ) { length = index + 1 ; if ( count >= length ) { settled = true ; concurrentPromise._resolveValue( values ) ; return ; } break ; } if ( settled ) { break ; } const promiseIndex = ++ index ; running ++ ; //console.log( "Launch" , promiseIndex ) ; Promise.resolve( value ) .then( value_ => { if ( settled ) { return ; } return iterator( value_ , promiseIndex ) ; } ) .then( value_ => { //console.log( "Done" , promiseIndex , value_ ) ; if ( settled ) { return ; } values[ promiseIndex ] = value_ ; count ++ ; running -- ; //console.log( "count/length" , count , length ) ; if ( count >= length ) { settled = true ; concurrentPromise._resolveValue( values ) ; return ; } if ( running < limit ) { runBatch() ; return ; } } , error => { if ( settled ) { return ; } settled = true ; concurrentPromise.reject( error ) ; } ) ; } } ; runBatch() ; if ( index < 0 ) { concurrentPromise._resolveValue( values ) ; } return concurrentPromise ; } ; /* Like native Promise.race(), it is hanging forever if the array is empty. It resolves or rejects to the first resolved/rejected promise. */ Promise.race = ( iterable ) => { var settled = false , value , racePromise = new Promise() ; for ( value of iterable ) { if ( settled ) { break ; } Promise.resolve( value ) .then( value_ => { if ( settled ) { return ; } settled = true ; racePromise._resolveValue( value_ ) ; } , error => { if ( settled ) { return ; } settled = true ; racePromise.reject( error ) ; } ) ; } return racePromise ; } ;