In the daily development process, from time to time, we will have to preload several images at the same time, and wait until they are all loaded before working. Combining Promise and async/await code will be more elegant, but it is also easy to encounter a pit. Today, we will briefly talk about it.

ES5

Let’s start with ES5. The basic idea is to make a counter. Every time the image triggers onload, it increases by one.

var count = 0,
	 imgs = [];
	 
function loadImgs(imgList, cb) {
	imgList.forEach(function(url, i) {
		imgs[i] = new Image();
		imgs[i].onload = function() {
			if( ++count === imgList.length) { cb && cb() } } imgs[i].src = url; })}Copy the code

Call method:

loadImgs(["xxx/a.png"."xxx/b.png"].function() {
	console.log("Get to work.");
})
Copy the code

The basic functionality is fine, but the code gets messy when the callbacks jump around.

The supreme state of asynchronous programming, as the saying goes, is that you don’t care if it’s asynchronous at all. Being able to write asynchronous code in a synchronous manner is a good coding experience. As a result, promises and async/await are introduced.

ES6

Let’s rewrite this with Promise and async/await. (Note that there is a big problem with this example.)

async function loadImgs(imgList, cb) {

    console.log("start")
    for( var i =0; i<imgList.length; i++) {
        await imgLoader(imgList[i], i);
        console.log("finish"+i)
    }
    cb();
}

async function imgLoader(url, num){
    return new Promise((resolve, reject) = > {
        console.log("request"+num)
        
        setTimeout(resolve, 1000);
        // let img = new Image();
        // img.onload = () => resolve(img);
        // img.onerror = reject;

        console.log("return"+num)
    })
}

loadImgs(["xxx/a.png"."xxx/b.png"].function() {
	console.log("Get to work.");
})
Copy the code

To make it easier to run code in a Node environment, I use setTimeout instead of actual image loading.

The result of the run is:

Start request0 return0 finish0 request1 return1 finish1 Starts workingCopy the code

Do you see any problems? Although we expect to write asynchronous effects in synchronous code, and we use async/await Promise and other exciting things, the actual results are synchronous. Request1 is issued after the request0 finish.

Although such code semantics clear, easy to understand, but such as a picture a sequential loading is not acceptable, at the same time issued several requests asynchronous loading is our goal.

The reason for this error is that async/await is just syntactic sugar. It is not async or await. It is essentially to solve the problem of too many nested callback.

The callback function

Years ago everyone rolled up their sleeves and jumped on the front end bandwagon by handing out jQuery weapons, but one of the big problems they ran into was “callback hell”.

In this example, three Ajax requests are sent before work can begin.

$.ajax({
    url: "xxx/xxx".data: 123.success: function () {
        $.ajax({
            url: "xxx/xxx2".data:456.success: function () {
                $.ajax({
                    url: "xxx/xxx3".data:789.success: function () {
                        // It's time to get down to business}})}})Copy the code

This is just a simple code structure to write out, brackets are dazzling, if you add business logic, error handling, and so on, it is a real “hell”.

The Promise of a savior?

Promise improved callback hell and was written much more synchronously.

Simply put, a Promise is a container that holds an event that has already happened and will end in the future, and when that event ends, a unified interface is called to tell you.

var promise = new Promise(function(resolve, reject) {
    $.ajax({
        url: "xxx/xxx3".success: function () {
           resolve(rs)
        },
        
    })
}

// when called
promise.then(function(rs){
	// Return another Promise
	return new Promise(...). }) .then(function(rs){
	// Return another Promise
	return new Promise(...). }) .then(function(rs){
	// Get to work
})
.catch(function(err){
	/ / make a mistake
});
Copy the code

Promise’s constructor takes two arguments, resolve and Reject, that are provided by the javascript engine and not implemented by itself.

  • The effect of resolve is to change the state of a Promise from “unfinished” to “resolved,” where the asynchronous operation completes and the result can be passed as a parameter to the next step.
  • Reject works by changing the state of the Promise from “unfinished” to “failed,” where the asynchronous operation fails and the error is passed on.

The then method can take two functions as arguments corresponding to resolve and reject, of which reject is optional.

promise.then(function(value) {
  // success
}, function(error) {
  // failure
});
Copy the code

Promise has at least saved the majority of developers from callback hell by turning callbacks into chained calls.

Note that ajax is used as an example, but jQuery ajax is promised and can be used in a similar way.

$.ajax({
  url: "test.html".context: document.body
}).done(function() {$(this ).addClass( "done" );
});
Copy the code

This is a lot more intuitive than writing the callback function, but there is still some nesting, which is not intuitive.

Async/await arrival

There is also a Generator between Promise and async/await. We don’t use it much, but it looks like this:

function* gen(x){
  var y = yield x + 2;
  return y;
}

var g = gen(1);
g.next() // { value: 3, done: false }
g.next(2) // { value: 2, done: true }
Copy the code

Generator functions are identified by *, paused by yield, split by yield into several parts, and each call to Next returns an object representing the current stage (value and done properties). The value attribute is the value of the expression following the yield statement and represents the value of the current phase; The done attribute is a Boolean value indicating whether the Generator has completed execution, that is, whether there is another phase.

Detailed information about the Generator can refer to www.ruanyifeng.com/blog/2015/0…

Async /await is the syntactic sugar of Generator, replacing * with async, a more explicit identifier, and yield with await.

Having said all that, we understand that async/await is about being able to write asynchronous code in a synchronous manner and solve callback hell at the same time.

So in the case of multiple images loading asynchronously, we would expect to be told when multiple asynchronous operations are complete.

async function loadImgs(imgList){
    let proList = [];
    for(var i=0; i<imgList.length; i++ ){
        let pro = new Promise((resolve, reject) = > {
            console.log("request"+i)
            setTimeout(resolve, 2000);
            console.log("return"+i)
        })
        proList.push(pro)
    }

    return Promise.all(proList)
            .then( (a)= >{
                console.log("finish all");
                return Promise.resolve(); })}async function entry(imgList, cb) {
    await loadImgs(imgList);
    cb();
}

entry(["xxx/a.png"."xxx/b.png"].function(){
    console.log("Get to work.")})Copy the code

The results are as follows:

Request0 return0 request1 return1 Finish All The task startsCopy the code

You’ll see it at the beginning and print it out immediately

request0
return0
request1
return1
Copy the code

Two seconds later, Finish All is printed.

A complete example

After understanding the process, let’s try it out in the browser. For compatibility issues, we’ll use WebPack to convert it.

The code:


function loadImgs(imgList){
    let proList = [];
    for(var i=0; i<imgList.length; i++ ){
        let pro = new Promise((resolve, reject) = > {
            let img = new Image();
            img.onload = function(){
                resolve(img)
            }
            img.src = imgList[i];
        })
        proList.push(pro)
    }

    return Promise.all(proList)
            .then( (rs) = >{
                console.log("finish all");
                return Promise.resolve(rs); })}async function entry(imgList, cb) {
    try {
        let rs = await loadImgs(imgList);
        cb(rs);
    } catch(err) {
        console.log(err)
        cb([])
    }
    
}

var imgUrlList = [
    "http://111.231.236.41/vipstyle/cartoon/v4/release/pic/index/recomment-single-s3.png"."http://111.231.236.41/vipstyle/cartoon/v4/release/pic/index/recomment-single-s2.png"
]
entry(imgUrlList, function(rs){
    console.log("Get to work.")
    console.log(rs)
})
Copy the code

Note that the Promise object after await can be rejected, so it is best to put it in a try… Execute in catch block.

To convert with webpack, please refer to webpack.config.js:

module.exports = {
  entry: ['./index.js'].output: {
    filename: 'bundle.js'
  },
  devtool: 'sourcemap'.watch: true.module: {
    loaders: [{
      test: /index.js/.exclude: /(node_modules)/.loader: 'babel'.query: {
        presets: ['es2015'.'stage-3'].plugins: [["transform-runtime", {
            "polyfill":false."regenerator":true}}]}}Copy the code

When you’re done, write a page and run it in your browser, open console, and you’ll see

The result returned is two picture objects, which we expect.

Check whether the network is concurrent:

Ok, done.

one more thing

In fact, the above step about async/await loading of images has been finished, here we go back to the generated file, we will find it is very large, just a few lines of code generated file is 80K.

Print out what packages webpack has made:

Our original index.js is only 4.08K, but Webpack packs a 24K runtime.js file to support async/await, and a bunch of other files to support ES6 syntax.

If you use Babel-Polyfill when you’re packing, the files you end up with can be a scary 200K.

TypeScript comes to mind.

TypeScript has excellent self-compilation capabilities, doesn’t need to introduce Babel, and does it better than Babel. In my example above, after installing TypeScript, you don’t need to change anything, just change the suffix to TS and start compiling.

Get a feel for it:

Bundle-ts.js is compiled in TypeScript at 5.5K.

Look at the implementation of async/await in the compiled file. Less than 40 lines clean.

TypeScript compiles files depending on how many features you use, and Bable will probably pack a bunch of them in at first, even if you don’t use them yet, and some implementations of TypeScript are better than Bable.

Of course, this is not to say that TypeScript is better than Bable. It depends on your project, but TypeScript is definitely worth your time.

conclusion

Sometimes we should not only look at the surface of the problem, but also look at the evolution of a thing, such as async/await at first sight, and think that adding async/await is asynchronous, which is easy to go wrong. Time to think more about the story behind, there will be a deeper understanding, you and I encourage each other.

The relevant code

Github.com/bob-chen/de…

twitter

Record some thoughts and thoughts, write science and technology and humanities, write life conditions, write reading experience, mainly bullshit and feeling. Welcome to pay attention and exchange.

Wechat official number: poem and distance of programmer

Public ID: Monkeycoder-Life

Refer to the article

The meaning and usage of Generator functions

The meaning and usage of async functions