MEMEPh. ideas that are worth sharing...

Asynchronous Solutions - Promise and Await

Foreword


Asynchronous programming mode is becoming more and more important in the front-end development process. From the very beginning of XHR to the encapsulated Ajax are trying to solve the problems in the asynchronous programming process. With the arrival of the new ES6 standard, there are new solutions for handling asynchronous data streams. We all know that in traditional ajax requests, when there is a data dependency between asynchronous requests, ugly multi-layer callbacks may be generated, commonly known as 'callback hell', which is daunting. The emergence of Promise lets us say goodbye to callback functions and write more elegant asynchronous code . In practice, it turns out that Promises are not perfect. Async/Await is one of the most revolutionary features added to JavaScript in recent years. Async/Await provides an alternative way to make asynchronous code look like synchronous code . Next, we introduce these two schemes for dealing with asynchronous programming.

 

1. The principle and basic syntax of Promise


1. The principle of Promise

Promise is an encapsulation of asynchronous operations, which can add methods to be executed when the asynchronous operation succeeds or fails through an independent interface. The mainstream specification is Promises/A+.

There are several states in a Promise:

Pending can be converted to fulfilled or rejected and can only be converted once, that is to say, if pending is converted to fulfilled state, it cannot be converted to rejected again. And the fulfilled and rejected states can only be converted from pending, and cannot be converted to each other.

 

2. The basic syntax of Promise

<script src="https://cdn.bootcss.com/bluebird/3.5.1/bluebird.min.js"></script>//If the lower version browser does not support Promise, use cdn in this way
<script>
function loadImg(src) {
var promise = new Promise(function(resolve, reject) {
var img = document.createElement('img')
img.onload = function() {
resolve(img)
} 

img.onerror = function() {
reject('image failed to load')
} 
img.src = src

})

return promise
}

var src = 'https://www.rendc.com/cdn/logo/logo.gif'
var result = loadImg(src)
result.then(
function(img) {
console.log(1, img.width)
return img
},

function() {
console.log('error 1')
}
).then(function(img) {
console.log(2, img.height)
})
</script>

 

2. Multiple serial operations of Promise

Promise can also do more things, for example, there are several asynchronous tasks, you need to do task 1 first, if successful, then do task 2, if any task fails, it will not continue and execute the error handling function. To execute such asynchronous tasks serially, without Promises, you need to write layers of nested code.

With Promises, we simply write job1.then(job2).then(job3).catch(handleError);

where job1, job2 and job3 are Promise objects.

For example, we want to load the second image after the first image is loaded. If one of them fails to execute, execute the error function:

var src1 = 'https://www.rendc.com/cdn/logo/logo.gif'
var result1 = loadImg(src1) //result1 is a Promise object
var src2 = 'https://memeph.com/cdn/logo/icon.jpg'
var result2 = loadImg(src2) //result2 is a Promise object
result1.then(function (img1) {
console.log('The first image is loaded', img1.width)
return result2 // chain operation
}).then(function (img2) {
console.log('The second image is loaded', img2.width)
}).catch(function (ex) {
console.log(ex)
})

Note here: the then method can be called multiple times by the same promise, and the then method must return a promise object . In the above example, if result1.then does not return a Promise instance in plain text, it defaults to its own Promise instance, which is result1, and result1.then returns an instance of result2, which is executed later. Then actually executes result2.then

 

3. Common methods of Promise

In addition to executing several asynchronous tasks serially, Promises can also execute asynchronous tasks in parallel .

Imagine a page chat system. We need to obtain the user's personal information and friend list from two different URLs respectively. These two tasks can be executed in parallel, using Promise.all() to achieve the following:

var p1 = new Promise(function (resolve, reject) {
setTimeout(resolve, 500, 'P1');
});
var p2 = new Promise(function (resolve, reject) {
setTimeout(resolve, 600, 'P2');
});
// Execute both p1 and p2, and execute then after they both complete:
Promise.all([p1, p2]).then(function (results) {
console.log(results); // get an Array: ['P1', 'P2']
});

Sometimes, multiple asynchronous tasks are for fault tolerance. For example, to read the user's personal information from two URLs at the same time, you only need to get the result returned first. In this case, use Promise.race() to implement:

var p1 = new Promise(function (resolve, reject) {
    setTimeout(resolve, 500, 'P1');
});
var p2 = new Promise(function (resolve, reject) {
    setTimeout(resolve, 600, 'P2');
});
Promise.race([p1, p2]).then(function (result) {
    console.log(result); // 'P1'
});

Since p1 executes faster, Promise's then() will get the result 'P1'. p2 still continues to execute, but the execution result will be discarded.

Summary: Promise.all accepts an array of promise objects, and after all are completed, execute the success uniformly ;

Promise.race accepts an array of multiple promise objects and executes success as long as one completes

Next, we make some modifications to the above example to deepen our understanding of the two:

var src1 = 'https://www.rendc.com/cdn/logo/logo.gif'
var result1 = loadImg(src1)
var src2 = 'https://memeph.com/cdn/logo/icon.jpg'
var result2 = loadImg(src2)
Promise.all([result1, result2]).then(function(datas) {
  console.log('all', datas[0]) 
  console.log('all', datas[1])
})
Promise.race([result1, result2]).then(function(data) {
  console.log('race', data) 
})  

If we combine Promises, we can combine many asynchronous tasks to execute in parallel and serially

 

4. Introduction and usage of Async/Await


Asynchronous operations are a hassle in JavaScript programming, and many people consider async functions to be the ultimate solution for asynchronous operations.

 

1. Introduction to Async/Await

 

2. Usage of Async/Await

function loadImg(src) {
const promise = new Promise(function(resolve, reject) {
const img = document.createElement('img')
img.onload = function() {
resolve(img)
} 

img.onerror = function() {
reject('image failed to load')
} 
img.src = src
})
return promise

}
const src1 = 'https://www.rendc.com/cdn/logo/logo.gif'
const src2 = 'https://memeph.com/cdn/logo/icon.jpg'
const load = async function() {
const result1 = await loadImg(src1)
console.log(result1)
const result2 = await loadImg(src2)
console.log(result2)
}
load()

When the function is executed, once it encounters await, it will return first, wait until the triggered asynchronous operation is completed, and then execute the following statement in the function body.

 

5. Async/Await error handling


The Promise object after the await command may be rejected, so it is better to put the await command in the try...catch code block. try..catch error handling is also more in line with the logic we usually handle when writing synchronous code .

async function myFunction() {
  try {
    await somethingThatReturnsAPromise();
  } catch (err) {
    console.log(err);
  }
}

 

6. Why is Async/Await better?


Async/Await has many advantages over Promise, three of which are described below:

 

1. Concise

Using Async/Await obviously saves a lot of code. We don't need to write .then, we don't need to write anonymous functions to handle the resolve value of Promise, we don't need to define redundant data variables, and we avoid nested code.

 

2. Intermediate value

You've likely encountered a scenario where promise1 is called, promise2 is called with the result returned by promise1, and promise3 is called with the result of both. Your code will most likely look like this:

const makeRequest = () => {
  return promise1()
    .then(value1 => {
      return promise2(value1)
        .then(value2 => {        
          return promise3(value1, value2)
        })
    })
}

Using async/await makes the code incredibly simple and intuitive

const makeRequest = async () => {
  const value1 = await promise1()
  const value2 = await promise2(value1)
  return promise3(value1, value2)
}

 

3. Conditional Statements

In the following example, you need to obtain data, and then decide whether to return directly or continue to obtain more data according to the returned data.

const makeRequest = () => {
  return getJSON()
    .then(data => {
      if (data.needsAnotherRequest) {
        return makeAnotherRequest(data)
          .then(moreData => {
            console.log(moreData)
            return moreData
          })
      } else {
        console.log(data)
        return data
      }
    })
}

Code nesting (6 levels) is less readable, all they convey is that the final result needs to be passed to the outermost Promise. Writing with async/await can greatly improve readability:

const makeRequest = async () => {
  const data = await getJSON()
  if (data.needsAnotherRequest) {
    const moreData = await makeAnotherRequest(data);
    console.log(moreData)
    return moreData
  } else {
    console.log(data)
    return data    
  }
}