seventh
Version:
A lean Promises and Async lib for ES6/ES7
622 lines (475 loc) • 13.4 kB
JavaScript
/*
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.
*/
;
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 ;
} ;