Use JavaScript to get original dimensions of an image file

Most of the time HTML images can take care of themselves. By default, an <img> element will load an image file and display it at its natural width on the page. If the file is 200px wide and 400px tall, that’s how big it will be when it loads.

If we need to adjust the size of the image, or how it is cropped or stretched or otherwise positioned, we can use CSS.

However, there are still times when we need to use JavaScript to manually work with an image, and often we need the image’s natural dimensions to do so. For example, we may need to paint the image on an HTML canvas.

Getting this information is easy, as the HTMLImageElement API includes a width and height property that expose the natural dimensions of the image file. Let’s see those properties in action in JavaScript:

const myImage = document.querySelector('')
const { width, height } = myImage
console.log(`The image is ${width}px wide and ${height}px tall.`)

Determine any image file’s size

There are cases when we need to know the size of an image that hasn’t been loaded yet. When we first load a web page, the HTML is available before any image data has loaded. The HTML is parsed into the DOM, and the page is rendered. Many times we see the blink where the page has rendered with no image and the image finishes loading later.

This delay can cause problems if we need to use JavaScript to determine the size or behavior of elements around the image. Before the image has loaded, its naturalHeight and naturalWidth are both zero. If our code tries to lay out a web page based on the size and aspect ratio of the image before it loads, it will treat the image as if it had no size at all.

To get around this, we have to wait for the image data to be available in the browser. Once the image is loaded we can execute our JavaScript to resize and reposition elements based on the image.

Here’s an example:

// Returns the original dimensions of an image file via callback
function getImgSize(imgSrc, callback) {
  const newImg = new Image();

  newImg.onload = function() {
    const height = newImg.height;
    const width = newImg.width;
    callback({ width, height });

  newImg.src = imgSrc; // this must be done AFTER setting onload

This function takes the URL of an image (imgSrc) and waits until the image has loaded. At that point, the callback function is called with the image’s natural width and height.

To accomplish this, we first create an HTMLImageElement. This is the same element that is created in the DOM by an HTML <img> tag.

HTMLImageElements have a width and height property. If a width and height attribute exist on an <img> element in HTML, those values will be returned. That means they are not the natural width and height of the image data being displayed. However, if an image does not have a width and height attribute manually set, it will return the natural width and height of the image data.

It is important that we wait for the image data to load in the browser. We do this by listening for the onload event. This event fires as soon as the image data is available in the browser. We have to be careful here – the image data might already be cached in the browser from a previous request, meaning the onload event will fire as soon as the image has an src. To ensure our onload callback is actually called, we have to add it before we give the image an src, which triggers the image data to load (from the browser cache or if necessary from a new request).

Other Applications

Another way we can take advantage of the onload event listener is to preload multiple images in sequence without overwhelming the connection to the server. For example:

// This is a Promise-based async version
const loadImage = (imageSrc) => new Promise(resolve => {
  const image = new Image();
  image.onload = () => {
    const height = image.height;
    const width = image.width;
    resolve({ image, width, height });
  image.src = imageSrc;

const imageUrls = ['image1.png', 'image2.png', 'image3.png'];

const run = async () => {
  for (const imageUrl of imageUrls) {
  	const { image, width, height } = await loadImage(imageUrl);
    // do something with `image`, `width`, and `height`


How to determine which JavaScript file is running

In programming, context is important. Part of that context is the currently executing script file.

For example, I might need to know which directory the current JavaScript file is running in so I can refer to other resources using a relative path. Relative paths are more convenient to work with than typing out absolute paths for everything. Also, if I were using absolute paths and I moved a file or directory in my code project, I would have to update all of the absolute paths inside. So there are valid reasons for wanting to know which script file we are currently in.

Node.js makes it extremely easy to determine the current file or directory with __filename and __dirname, respectively.

Fortunately, it’s not too difficult to find the same information in the browser using document.currentScript. This property returns the <script> element that is currently being executed.



There are a few caveats to using document.currentScript. First, the property only references its containing <script> element if the code is executing synchronously. That means we can’t use it in callbacks and event handlers. Fortunately, this is easy to get around:

const currentScript = document.currentScript

function myCallback() {

setTimeout(myCallback, 1000)

By saving a reference to the script file when the code runs synchronously, we are able to reference the document.currentScript property later in our asynchronous code.

Another caveat is that document.currentScript doesn’t work in JavaScript modules. That’s okay. We can just use the import.meta property there instead.

Internet Explorer

document.currentScript is supported by all modern browsers, but doesn’t have support in Internet Explorer. You can still use this snippet to achieve the same effect:

var currentScript;
if (document.currentScript) {
  currentScript = document.currentScript
} else {
  var scripts = document.getElementsByTagName('script')
  currentScript = scripts[scripts.length - 1]
console.log('Script located at: ' + currentScript.src)

We will query for all script elements on the page and then grab the last one by its index. With few exceptions, the currently executing script will be the last script element added to the DOM, as any later script elements haven’t been loaded yet.

One rare exception where this won’t work is if you dynamically append a script within the DOM, such as in the head of the document, after page load. If there were script elements in the body of the HTML document, the last one would be returned instead of the currently executing script that was added later.

The good news is that if you are manually appending scripts to the DOM, you can ensure those dynamically added scripts are being added at the end of the body where this script will successfully identify them in Internet Explorer.

Load JavaScript files dynamically

Usually when we need to include a JavaScript file on an HTML page we just do this:

<script src="the-javascript-file.js"></script>

And with modern JavaScript maybe we throw an async or defer attribute on that script tag for a little extra performance. Better yet, we could set type="module" to use the JavaScript module system.

If we are using JavaScript modules, we can include other JavaScript module files directly by using an import statement:

import otherModule from '/other/module.js'

However, there are times when none of these options are available. For example, if we don’t have access to edit the original HTML markup being served, we are forced to load JavaScript dynamically.

Real world use cases for this include bookmarklets and web extensions.

Loading JavaScript dynamically

A <script> element can be created and appended to the DOM just like any other HTML element. For example:

const script = document.createElement('script')
script.src = '/my/script/file.js'

Once a script element has been appended to the DOM, it will be executed. This means that inline scripts will have their contents interpreted and executed as JavaScript just as we would expect if they had been part of the HTML when it was first loaded. Similarly, external script files will be loaded and executed.

Here’s an inline example:

const inlineScript = document.createElement('script')
script.innerHTML = 'alert("Inline script loaded!")'

As you can see, it’s easy to create and append new script elements, allowing us to include any number of external JavaScript files dynamically after a page has loaded.

Determining when a JavaScript file is loaded

The real challenge isn’t loading the file – it’s knowing when the file has finished loading. For example, maybe we have code that uses a library like jQuery or AngularJS or Vue (listed in order of ancientness, not preference). We need to make sure the library is loaded before we execute our own code, otherwise our code will break.

We could do something silly like call setInterval and continually check if the library has loaded by looking for its global window variable:

const jqueryScript = document.createElement('script')
jqueryScript.src = ''

const jqueryCheckInterval = setInterval(() => {
  if (typeof window.jQuery !== 'undefined') {
	// do something with jQuery here
}, 10)

However, this code is ugly and wastes resources. Instead, we should listen directly for the script element to fire its onload event:

const jqueryScript = document.createElement('script')
jqueryScript.src = ''
jqueryScript.onload = () => {/* do something with jQuery */}

We’ve already cut the size of our code in half, making it much easier to read and work with. It’s also slightly more performant.

The code would be even easier to read if we used Promises, which would allow us to chain multiple scripts together to load one after the other. Here’s a function we can use:

 * Loads a JavaScript file and returns a Promise for when it is loaded
const loadScript = src => {
  return new Promise((resolve, reject) => {
    const script = document.createElement('script')
    script.type = 'text/javascript'
    script.onload = resolve
    script.onerror = reject
    script.src = src

Notice we have also introduced error handling by listening for the script element’s onerror event.

Here’s what the script looks like in action:

  .then(() => loadScript(''))
  .then(() => {
    // now safe to use jQuery and jQuery UI, which depends on jQuery
  .catch(() => console.error('Something went wrong.'))

Keeping Things Fresh

The above script works great for libraries and modules that never change, such as those loaded from a CDN. Once the script is loaded, the browser will automatically cache it. The next time the script is needed, the browser will reuse the copy it saved earlier. This saves bandwidth and makes the page load faster.

This built-in behavior is actually a problem for scripts that change. For example, you might want to create a bookmarklet with just enough code to load an external script, allowing that script to do all of the heavy lifting. That script might change over time as you add new features. If you use the above loadScript function, those new features might not show up because the browser has already cached your script, and it now reuses that cached version instead of checking your server.

To ensure your script is actually loaded from your server each time, you can add a meaningless query value to the end of the script URL. As long as this value is different each time the script is loaded, it will cause the browser to treat the URL as a new resource and load it directly from the server each time.

Here’s what that can look like in code:

 * Loads a JavaScript file and returns a Promise for when it is loaded
const loadScriptNoCache = src => {
  return new Promise((resolve, reject) => {
    const url = new URL(src)
    url.searchParams.set('random', Math.random())
    const script = document.createElement('script')
    script.type = 'text/javascript'
    script.onload = resolve
    script.onerror = reject
    script.src = url.toString()

Dueling with dinosaurs

If you don’t have access to the original HTML source of the page you’re working with, there’s a chance you’re facing other limitations as well. For example, you could be forced to work with Internet Explorer.

IE may be old and behind the times, but thankfully we can accommodate it with just a few modifications. First, we need to drop the Promises API and go back to using callbacks. Second, we need to account for IE’s unique way of handling script load events. Namely, IE doesn’t fire an onload event, but it does give scripts an onreadystatechange event just like XMLHttpRequests.

Here’s the callback-based version that works with Internet Explorer as well as other browsers:

 * Plays well with historic artifacts
function loadScript(src, callback) {

  var script = document.createElement('script')
  script.type = 'text/javascript'

  // IE
  if (script.readyState) {
    script.onreadystatechange = function () {
      if (script.readyState === 'loaded' || script.readyState === 'complete') {
        script.onreadystatechange = null
  // Others
  else {
    script.onload = callback

  script.src = src

Use JavaScript to condense HTML markup and remove extra space

This technique is a little bizarre and probably doesn’t have many direct applications. I’m going to share it because it lets me talk about regular expressions, and it’s food for thought about other ways to use them.

The problem

We have a DOM element that we want to serialize (basically, we want to store it as a string). The problem is, there is a ton of extra white space in the element’s HTML markup. If we stored that white space in a JavaScript variable we would be wasting resources. If we have to store many elements like this, the problem is even bigger!

We need a way to remove space from a string, but not just any space. We only want to remove white space characters that occur between HTML elements.

<p>We want to keep all space between these words.</p>

<p>We want to remove the blank line above this paragraph.</p>

<span>Also, </span>          <span>the space between these two spans</span>

A simple solution

Here’s the answer:

function condenseHTML(elem) {
    const html = elem.outerHTML
    const condensedHtml = html.replace(/>\s+</g, '><')
    return condensedHtml

Let’s walk through this:

  • Our function accepts a single argument: an HTMLElement.
  • We then get the HTML that represents this element and its children using the element’s outerHTML property.
  • Next, we use a regular expression to execute a search and replace operation.

Let’s talk about that regular expression:

  • The regular expression looks for one or more white space characters (\s matches white space characters, + matches one or more).
  • Specifically, we are looking for white space between > and < characters. In other words, white space that appears after the end of one HTML element and before the beginning of the next. Notice that we are matching the > and < as well.
  • Also notice that the regular expression has the g flag at the end, which stands for “global”. This means it can find multiple matches in the same string.
  • We will then replace all the matches by using the string’s replace method.

Since our regular expression matched the > and < characters, they will also be replaced. That’s why we are replacing each individual match with ><, as otherwise the angular brackets would be lost. Now the HTML elements remain valid but there will no longer be space between them.


This approach isn’t perfect. Technically, we are removing space between elements that really should be there. See this example:

<span>We still need</span>         <span>space here.</span>

The space between these two spans will all be removed, which means the words “need” and “space” will be smashed together when viewed in the browser. To be extra safe, our solution could replace multiple spaces with a single space instead of replacing them all with no spaces.

Remember that in HTML, multiple spaces usually condense down to a single visual space (unless the element’s style has white-space set to pre, pre-wrap, or break-spaces).

Extended learning

If you’d like to get more practice using regular expressions, I recommend checking out

If you’re interested in further optimizing HTML serialization, try using a DOMParser to parse the HTML source into a live Document. You can then use the context of each Element and Text node to determine which spaces are safe to remove.

Read console input with Node.js

When people first learn a programming language, they often start by creating small programs that run in the terminal (a.k.a. “console” a.k.a. “command prompt”). The program will give some text output, then the user will type some text input, then the program will read it and give some more output.

One such exercise could be: create a program that asks the user for their name, allows them to type their name, then greets them by name.

There are many programming tutorials that teach Python and Ruby and other scripting languages, and many of them start here, with simple console programs. On the other hand, Node.js tutorials usually start by creating a simple web server. That’s super cool, but it means most Node.js developers aren’t very familiar with how the terminal works in their own programming language.

Interacting with the terminal

First, we need a way to write to the terminal and also a way to read data that was entered in the terminal:

stdin is the standard input stream, and stdout is the standard output stream. This pattern exists across programming languages. To simplify, these streams allow data to pass in and out of the program through the terminal.

Here’s a one-liner example of how to write to stdout:

process.stdout.write('Hello world!')

And here’s a simple example of how to read from stdin:

process.stdin.once('data', data => {
  console.log('Data entered: ' + data)

We added a one-time event listener for a 'data' event from the stream, which happens when someone types input and presses Enter.

Since stdin is a Readable stream, it begins in paused mode. Adding a 'data' listener switches the stream to flowing mode. When stdin is in flowing mode, it listens for data from the terminal.

Once the 'data' event triggers, we can work with the input from the console. In this case, we will just log some output to the console. Notice how the global console instance is already configured to write to stdout!

Also notice how we had to call process.exit() to exit the program. Since we left the process.stdin stream open, the program was still listening for user input. Calling process.exit() is one way to terminate the program. A more proper approach would be to close the stream by calling process.stdin.pause() in the same place.

Keeping things organized

Since Node.js is asynchronous, things are going to get a little weird. When you ask the user for input, Node.js doesn’t wait for them to type something. It just keeps on working. So it’s important that we keep things as organized as possible so we don’t lose track of what’s happening.

Fortunately, the built-in readline module simplifies things for us:

const readline = require('readline')

const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout

rl.question('What is your name?', nameAnswer => {
  console.log(`Nice to meet you, ${nameAnswer}.`)

Once we create a readline interface, we don’t have to worry about using process.stdin and process.stdout directly.

Notice that readline uses callbacks. If we want to ask several questions in sequence, we have to juggle those callbacks somehow:

rl.question('What is your favorite color?', colorAnswer => {
  console.log(`I like ${colorAnswer} too.`)

  rl.question(`What shade of ${colorAnswer} is best?`, shadeAnswer => {
    console.log(`Wow, ${shadeAnswer} is also my favorite!`)

Of course, JavaScript has a solution for this. We can use Promises to avoid nesting callbacks ad nauseam:

const question = prompt => {
  return new Promise((resolve, reject) => {
    rl.question(prompt + '\n', resolve)

Conveniently, we have abstracted away all interaction with the standard input and output streams. That leaves you to focus on the higher-level concern of what text should display and what to do with the text typed by the user. We have also abstracted away the pyramid of callbacks, so we can more easily see line-by-line how the terminal interaction will go.

Putting it all together

Here’s a final complete example of our setup, which is inside an async function so we can use await to inline our Promises:

const readline = require('readline')

const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout

const question = prompt => {
  return new Promise((resolve, reject) => {
    rl.question(prompt, resolve)

(async () => {
  const nameAnswer = await question('What is your name?')
  console.log(`Nice to meet you, ${nameAnswer}.`)
  const whereAnswer = await question('Where are you from?')
  console.log(`I hear it's nice in ${whereAnswer}.`)

Read a file “upload” with JavaScript

In my last post I showed how to create file downloads with JavaScript.

That’s only part of the picture. If we want our users to be able to continue working with their data after they’ve downloaded it, we also need to support file uploads.

Why not use another storage option?

The specific use case we are talking about is a project where we don’t have access to any sort of backend server or database, but we still need a way for our users to save their work. One option is to just store the data in the browser, but all of our in-browser options have significant limitations:

  • If we used session storage, the data would disappear as soon as the browser was closed. In most cases, we need the data to persist between browser sessions.
  • We could use HTTP cookies to store data, but cookies have a maximum size of 4096 bytes per domain. When it comes to storing data, that’s probably not enough. Also, since cookies are transferred to the server with every request, we would be bogging down our interactions with the server. Wait! There is no server! In that case, our users are likely viewing our HTML file using the file:// protocol, which means cookies can’t be set in the first place.
  • We could step up our game with local storage or IndexedDB, which both have reasonable size limits and can store data across browser sessions. The major problem here is that they are browser-specific. Our users can’t use their data in other browsers on other computers. They also can’t export their data to be used in other programs.

If we allow users to just save data to the filesystem, none of the above drawbacks apply. Files don’t disappear when the browser is closed or the user clears their cookies. There is practically no size limit. Most importantly, files are portable and can be used in other browsers, on other computers, and even in other applications.

Reading an uploaded file

In order to read an uploaded file, we will first need an HTML form to which we can attach our “upload.” Of course, we aren’t really uploading anything since we don’t have a server. We just need a file <input> element to let our JavaScript read a file from the local filesystem.

<input type="file" id="file-to-read">

Well that was about as easy as it gets. That one-liner will allow us to select a file on our computer to “upload.” Now let’s add a button that we can click to initiate reading the selected file.

<button onclick="readFileAsText()">Load Selected File</button>

Piece of cake. This is just a regular button with an event listener. When we click it, the readFileAsText function is called.

Let’s finish up by defining that function:

  const readFileAsText = function() {
    const fileToRead = document.getElementById('file-to-read').files[0]
    const fileReader = new FileReader()
    fileReader.addEventListener('load', function(fileLoadedEvent) {
      const textFromFileLoaded =

    fileReader.readAsText(fileToRead, 'UTF-8')

Step by step, in English

  • The first thing we need to do is get a reference to the file that the user selected. We do that by finding the file input, in this case by its ID. Then we access its files property. Since a file input can allow multiple upload files to be selected, the files property is an array. We only care about the first item, so we will grab the item at index zero.
  • We will then create a FileReader, which allows us to asynchronously read data from a File or Blob.
  • It is important to realize what “asynchronous” means here. It means the FileReader will mind its own business while it is reading and won’t block the rest of our code from running. We need to add an event listener for its load event so we can work with the result when it is finished reading.
  • In our event listener we need to reference the FileReader because it now has a populated result property. We can always do this by using (Since this particular example has a traditional function and not an arrow function we could also use this to refer to the FileReader.)
  • We are now free to do whatever we want with the contents of the file, which are a string. In this example, the file contents are logged to the console.
  • Remember that we haven’t actually told the FileReader to read the file yet – so far we have only told it what to do when it finishes reading a file. It is important to attach event listeners first. However, in order to get the FileReader to read a file in the first place, we need to call its readAsText method on the file from our <input>. This method causes the result to be a string. The FileReader class has other methods that cause result to be different types of data.

Managing the data

If you are going to allow your users to save and load data from your app, I recommend serializing that data in JSON form. If you do, the data will have a clear structure and will be easy to deserialize and load back into your application. It will also be easier for other applications and custom scripts to consume the data.

Create file downloads with plain browser JavaScript

Data is the heart of many applications. Users interact with data constantly, and the real value of many web apps is the simplicity with which they allow users to interact with their data.

One way to make data management more convenient is to just allow users to download the data they’re working with. In a world overrun with cloud computing, this sounds very old school. Still, there’s a time and place for everything.

We have a secret weapon for creating file downloads using only browser technology: the anchor tag’s download attribute. Check it out:

<a href="/files/nvc89340nc.txt"

Notice several details about the above code snippet:

  • This is a regular anchor tag, the same one we use to create hyperlinks
  • It has an href attribute pointing to a file on the server
  • It has a download attribute that specifies a filename that will be suggested when the file is downloaded

When I click this link, I am initiating a download of the file /files/nvc89340nc.txt on the server. When the “Save” dialog appears, it will suggest that I save the file as pretty-file-name.txt.

That’s great, but remember our use case: we don’t have a server. We want to take data that was created and manipulated in JavaScript and allow users to download it onto their personal machines.

There are two good ways to do that, depending on your use case. The first is to use a Blob and corresponding object URL. The second is to use a Data URL. Either option will allow you to turn JavaScript strings into files that can be downloaded by clicking a link.

Blob and Object URL

First, the code:

// Generates a text file download named `filename` with contents of `dataString`.
const generateDownload = (filename, dataString) => {
  const a = document.createElement('a')
  document.body.appendChild(a) = 'display: none'
  const blob = new Blob([dataString], {type: 'octet/stream'}),
        url = URL.createObjectURL(blob)
  a.href = url = filename

In this example, we first create an anchor to click and a Blob to contain our data. Then we create an object URL that references our Blob and assign that URL to our link’s href attribute. Finally, now that we have our data and a URL that points to it, we can “click” our anchor in JavaScript. This initiates the file download.

Afterward, we will clean up by revoking our object URL and removing our temporary link element.

Data URL

This option is more straightforward. As in, it uses less technologies and they are probably easier to understand:

// Generates a text file download named `filename` with contents of `dataString`.
const generateJSONDownload = (filename, dataString) => {
  const data = 'text/json;charset=utf-8,' + encodeURIComponent(dataString)
  const link = document.createElement('a') = 'none'

  const attrDownload = document.createAttribute('download')
  attrDownload.value = filename

  const attrHref = document.createAttribute('href')
  attrHref.value = 'data:' + data


URLs that begin with data: are Data URLs. They allow us to embed data into a URL. Check the MDN page for more examples.

In this particular example we will create a JSON file download. Data URLs contain the MIME type of their embedded data, which is text/json in the snippet below.

We will use encodeURIComponent to ensure our data can be safely added to the URL. Then we apply that URL to our anchor element’s href, add a download attribute, and click it. Viola! The string of data we provided will now download onto our machine.

Use Cases

This technique is especially valuable for single file HTML tools and bookmarklets, which both have limitations for how data can be stored and retrieved.

JavaScript ranges

If you have some experience with Ruby, you’re probably familiar with the Ruby range syntax:


A Ruby range is an enumerable object with a starting and ending value. The range above will iterate from 7 up to 11. Super easy!

Let’s see what that looks like in JavaScript:

for (let i = 7; i <= 11; i++) {

Oh, right. JavaScript doesn’t have ranges.

But wait… if the above for loop gets the job done, does JavaScript even need ranges? After all, Ruby is known for having a lot of shiny features that might secretly be bad ideas.

Well, check this out:

(7..11).include? 9 # true

my_range = (13..31)
my_range.include? 12 # false
my_range.include? 30 # true

Notice how the range can have its own methods because it’s an actual object? And we can store it and use it later? We can’t do that with JavaScript, and sometimes shiny new features like ranges have an actual purpose.

In this case, an actual range object makes it easier to read and manage our code since we are encapsulating the starting and ending value into a single object.

It would be nice if we could just do this in JavaScript:

for (let i of Range(7, 11)) {

const myRange = Range(4, -8)
if (myRange.includes(-1)) {
  console.log('We got a negative one')

Fortunately, it’s not hard to make our own JavaScript ranges. There are different ways of approaching this problem, including actual JavaScript iterators and generators.

To keep things simple, I’m just going to define a function that creates arrays of integers based on the from and to arguments I pass in:

const Range = (from, to) => {
  const increment = from < to ? 1 : -1
  let difference = Math.abs(to - from) + 1
  const result = []
  while (difference) {
    from += increment
  return result

Since this allocates an entire array in memory, it could get expensive if you are creating many ranges or very large ranges. You should definitely consider whether a regular for loop will get the job done before using this snippet.

On the other hand, if you have a script you pull out once a year that iterates over arbitrary ranges of integers, this is a great way to make it easier to revisit and maintain.

Creating JavaScript Promises that can be resolved externally

Promises were a huge step forward for JavaScript. Finally, a mess of nested callbacks could be replaced with a fluent API.

Still, the built-in Promise has some limitations. As a replacement for callback functions, Promises are designed to wrap and represent a single function. When you create a Promise you hand control over to it by giving it two callbacks: resolve and reject. Then you wait until the function calls one of them:

new Promise((resolve, reject) => {
  // do something asynchronous here
  // call `resolve` or `reject` when finished
.then(result => {
  // this code can't execute until `resolve` or `reject` is called above

This means a standard Promise is simply a more organized way to handle callback results and errors.

That’s a problem because it limits abstraction. If a Promise is only a callback object, we limit it to that particular use case. But Promises can be a more flexible means of control flow. They represent pending action, and they indicate when it is finished.

A Promise can exist on its own without wrapping a callback function. To make this happen, we just need to pull its resolve and reject methods outside the scope of its callback:

const defer = () => {

  let resolve, reject

  const deferred = new Promise((res, rej) => {
    resolve = res
    reject = rej

  deferred.resolve = resolve
  deferred.reject = reject

  return deferred

We now have a Promise with two additional methods: resolve and reject. We can use these together with then and catch to control flow any way we want.

Let’s break this down. defer is a factory function. It creates special Promise objects, so to use it we would assign the result to a variable:

const myPromise = defer()

We can attach callbacks to myPromise just like any other Promise:

  .then(result => {
    console.log('It finished!', result)
  .catch(err => {
    console.warn('Something went wrong', err)

Finally, anywhere in our program that myPromise is in scope, we can resolve or reject it:

if (err) {
else {

You might have noticed this looks a lot like a jQuery Deferred Object. This pattern gives us much of the same utility value without a library.

I’ll leave it to you to decide if it fits your current work.