Skip to content

Instantly share code, notes, and snippets.

@Rich-Harris
Last active August 27, 2022 14:59
Show Gist options
  • Star 30 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save Rich-Harris/11010768 to your computer and use it in GitHub Desktop.
Save Rich-Harris/11010768 to your computer and use it in GitHub Desktop.
ES6 Promise polyfill
( function ( global ) {
'use strict';
var Promise, PENDING = {}, FULFILLED = {}, REJECTED = {};
if ( typeof global.Promise === 'function' ) {
Promise = global.Promise;
} else {
Promise = function ( callback ) {
var fulfilledHandlers = [],
rejectedHandlers = [],
state = PENDING,
result,
dispatchHandlers,
makeResolver,
fulfil,
reject,
promise;
makeResolver = function ( newState ) {
return function ( value ) {
if ( state !== PENDING ) {
return;
}
result = value;
state = newState;
dispatchHandlers = makeDispatcher( ( state === FULFILLED ? fulfilledHandlers : rejectedHandlers ), result );
// dispatch onFulfilled and onRejected handlers asynchronously
wait( dispatchHandlers );
};
};
fulfil = makeResolver( FULFILLED );
reject = makeResolver( REJECTED );
callback( fulfil, reject );
promise = {
// `then()` returns a Promise - 2.2.7
then: function ( onFulfilled, onRejected ) {
var promise2 = new Promise( function ( fulfil, reject ) {
var processResolutionHandler = function ( handler, handlers, forward ) {
// 2.2.1.1
if ( typeof handler === 'function' ) {
handlers.push( function ( p1result ) {
var x;
try {
x = handler( p1result );
resolve( promise2, x, fulfil, reject );
} catch ( err ) {
reject( err );
}
});
} else {
// Forward the result of promise1 to promise2, if resolution handlers
// are not given
handlers.push( forward );
}
};
// 2.2
processResolutionHandler( onFulfilled, fulfilledHandlers, fulfil );
processResolutionHandler( onRejected, rejectedHandlers, reject );
if ( state !== PENDING ) {
// If the promise has resolved already, dispatch the appropriate handlers asynchronously
wait( dispatchHandlers );
}
});
return promise2;
}
};
promise[ 'catch' ] = function ( onRejected ) {
return this.then( null, onRejected );
};
return promise;
};
Promise.all = function ( promises ) {
return new Promise( function ( fulfil, reject ) {
var result = [], pending, i, processPromise;
if ( !promises.length ) {
fulfil( result );
return;
}
processPromise = function ( i ) {
promises[i].then( function ( value ) {
result[i] = value;
if ( !--pending ) {
fulfil( result );
}
}, reject );
};
pending = i = promises.length;
while ( i-- ) {
processPromise( i );
}
});
};
Promise.race = function ( promises ) {
return new Promise( function ( fulfil, reject ) {
promises.forEach( function ( promise ) {
promise.then( fulfil, reject );
});
});
};
Promise.resolve = function ( value ) {
return new Promise( function ( fulfil ) {
fulfil( value );
});
};
Promise.reject = function ( reason ) {
return new Promise( function ( fulfil, reject ) {
reject( reason );
});
};
}
// TODO use MutationObservers or something to simulate setImmediate
function wait ( callback ) {
setTimeout( callback, 0 );
}
function makeDispatcher ( handlers, result ) {
return function () {
var handler;
while ( handler = handlers.shift() ) {
handler( result );
}
};
}
function resolve ( promise, x, fulfil, reject ) {
// Promise Resolution Procedure
var then;
// 2.3.1
if ( x === promise ) {
throw new TypeError( 'A promise\'s fulfillment handler cannot return the same promise' );
}
// 2.3.2
if ( x instanceof Promise ) {
x.then( fulfil, reject );
}
// 2.3.3
else if ( x && ( typeof x === 'object' || typeof x === 'function' ) ) {
try {
then = x.then; // 2.3.3.1
} catch ( e ) {
reject( e ); // 2.3.3.2
return;
}
// 2.3.3.3
if ( typeof then === 'function' ) {
var called, resolvePromise, rejectPromise;
resolvePromise = function ( y ) {
if ( called ) {
return;
}
called = true;
resolve( promise, y, fulfil, reject );
};
rejectPromise = function ( r ) {
if ( called ) {
return;
}
called = true;
reject( r );
};
try {
then.call( x, resolvePromise, rejectPromise );
} catch ( e ) {
if ( !called ) { // 2.3.3.3.4.1
reject( e ); // 2.3.3.3.4.2
called = true;
return;
}
}
}
else {
fulfil( x );
}
}
else {
fulfil( x );
}
}
// export as node module
if ( typeof module !== 'undefined' && module.exports ) {
module.exports = Promise;
}
// or as AMD module
else if ( typeof define === 'function' && define.amd ) {
define( function () { return Promise; });
}
global.Promise = Promise;
}( typeof window !== 'undefined' ? window : this ));
@ScottRudiger
Copy link

ScottRudiger commented Oct 13, 2018

FYI, one issue I noticed is your implementation of Promise.all doesn't wrap non-promise values in a Promise like native Promise.all does. For example:

// this gist
(async () => {
  const r = await Promise.all([Promise.resolve(1), 2, Promise.resolve(3)]); // rejects with: TypeError: promises[i].then is not a function
  console.log(r);  // if this were a native `Promise.all`, it would log '[1, 2, 3]'
})();

I believe you could fix this by wrapping promises[i] in a Promise.resolve call; e.g.:

Promise.all = function ( promises ) {
  // ...
  processPromise = function ( i ) {
	Promise.resolve(promises[i]).then( function ( value ) {
		result[i] = value;

		if ( !--pending ) {
			fulfil( result );
		}
	}, reject );
  };
// ...
};

Also, this is really nit-picky, but I'd spell "fulfil" as "fulfill" to be consistent.

EDIT: Just realized who authored this gist; thanks for rollup! It really is wonderful.

@Paul-Browne
Copy link

A much lighter weight Promise.All alternative vowAll

@schester44
Copy link

vowAll is for xhr requests and isn't an alternative to Promise.all.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment