Promises

The built-in Promise object provides a unified interface for managing asynchronous tasks.

There are many situations where a JavaScript program will have to pause for an unknown duration of time before it can continue working, like waiting on a response from a database or the result of running an external program. In the early days of JavaScript, programmers relied on callback functions to manage these kinds of asynchronous tasks.

JavaScript with callbacks is hard to get right intuitively. A lot of code ends up looking like this:

fs.readdir(source, function (err, files) {
if (err) {
console.log('Error finding files: ' + err)
} else {
files.forEach(function (filename, fileIndex) {
gm(source + filename).size(function (err, values) {
if (err) {
console.log('Error identifying file size: ' + err)
} else {
aspect = (values.width / values.height)
widths.forEach(function (width, widthIndex) {
height = Math.round(width / aspect)
console.log('resizing ' + filename + 'to ' + height + 'x' + height)
this.resize(width, height).write(dest + 'w' + width + '_' + filename, function(err) {
if (err) console.log('Error writing file: ' + err)
})
}.bind(this))
}
})
})
}
})

Callbacks also introduce a potential source of bugs and memory leaks with variable scoping, where a variable with the same name is defined multiple times in nested closures.

fetchSomeData('http://example.com/data/...', (error, result) => {
if (error) {
sendErrorToLogs(error, function(error2, done) {
// ... more callbacks
})
}
else {
if (result.type == 'json')
sendSomeData(result, 'json', (error, result) => {
// ... quickly becoming cumbersome and error-prone
})
}
})

The use of Promise objects, along with the built-in keywords async and await, allow programmers to order sequential instructions for handling asynchronous tasks. It is common for TypeScript libraries and built-in Node.js modules to support a Promise-based approach to writing programs.

The Promise object

A common way to create a Promise is by constructing it with new Promise(...).

A Promise object must be initialized with a function that takes two parameters, which are often labeled resolve and reject (although you could call them success and failure, done and failed, etc).

Arrow functionAlternate parameter name
new Promise(function(resolve, reject) { ... })

This function is called immediately when the Promise is constructed. The arguments passed to resolve and reject are each functions which accept a single parameter.

Arrow functionAlternate parameter name
new Promise((
    
resolve: (value: any) => any,
    
reject: (error: any) => any,
) => { ... })

If resolve is called first, then the Promise object uses the value argument as the final result of the asynchronous task it represents. If reject is called first, then the Promise object enters an "error state" where it will produce the error instead.

then and catch

Once the Promise is created, you can use its then and catch methods to register callback functions that should be executed with the result of resolve and reject respectively. Try modifying the simple example below:

const myPromise = new Promise(function(resolve, reject) {
resolve('Hello!')
// Try commenting above and uncommenting below
// reject('Something bad happened')
})

myPromise.then(function(result) {
console.log('Result', result)
})

myPromise.catch(function(error) {
console.log('Error', error)
})
Click "Run" or Shift + Enter

The Promise will only call one callback function exactly once - the function registered with then, or the function registered with catch. Each of these function calls returns a new Promise which represents the result of performing some further (possibly asynchronous) work.

If either function returns a value, that value will be passed to subsequent callback functions registered with then or catch.

// Returns a promise that waits one second before resolving
const myPromise = new Promise(function(resolve) {
resolve('Hello!')
})

const nextPromise = myPromise.then(function(result) {
console.log('First result', result)

// Return another promise that waits 1 second before resolving
return new Promise((resolve) => {
setTimeout(function() {
resolve('How is the weather?')
}, 1000)
})
})

nextPromise.then(function(nextResult) {
console.log('Next result', nextResult)
})

// Notice this will be called with the result of the first promise, and
// produce a different Promise from myPromise and nextPromise
myPromise.then(function(result) {
console.log('Same result', result)
})
Click "Run" or Shift + Enter

Resolve and reject

The Promise.resolve and Promise.reject functions create Promise objects that immediately resolve or reject with the given values.

It's a useful shorthand for writing a full function to initialize a Promise object and immediately call resolve or reject with a particular value.

const hello = Promise.resolve('Hello world!')

const equivalent = new Promise((resolve) => {
resolve('Hello world!')
})

const error = Promise.reject('Something broke!')

The benefit of doing this, even for synchronous function that don't depend on asynchronous result, is that you can now use the function in a configurable chain of transformations that can include both synchronous and asynchronous work.

// Synchronous functions that return Promises
function double(value: number): Promise<number> {
return Promise.resolve(value * 2)
}
function sqrt(value: number): Promise<number> {
return Promise.resolve(Math.sqrt(value))
}

// An asynchronous function that returns a Promise
function wait(seconds: number) {
return new Promise((resolve) => {
setTimeout(function() {
resolve(seconds)
}, seconds * 1000)
})
}

// Create a chain of function calls that can easily be re-ordered
double(1).then(sqrt).then(wait).then(console.log)
sqrt(4).then(double).then(console.log)
Click "Run" or Shift + Enter

Async and await

The async keyword indicates that a function returns a Promise.

If the return type is not specified, TypeScript will attempt to infer the type of the returned Promise. If the function is missing type specifications, the return type will default to Promise<any>, or Promise<void> if the function does not return anything.

Asynchronous function
async function getResultAsync(): Promise<any> { ... }

One of the key advantages to using Promises is the ability to use async and await.

async function workWithData() {
const result = await getSomeData()
console.log(result)
}

async function getSomeData() {
return { some: 'data' }
}

workWithData()
Click "Run" or Shift + Enter

async functions

An async function must return a correctly-typed Promise object.

async function incorrect(): string {
return 'must return Promise<string>'
}

async function fetchData(): Promise<string> {
return 1234
}
Click "Run" or Shift + Enter

Was this page helpful?