What does inelegant code look like?

1. Callback hell

/** * Read package.json from current directory and back it up to backup directory ** 1. Json * 2. Check if the backup directory exists, if not create a backup directory * 3. ReadFile ('./package.json', function(err, data) {if (err) {console.error(err); } else { fs.exists('./backup', function(exists) { if (! exists) { fs.mkdir('./backup', function(err) { if (err) { console.error(err); } else { // throw new Error('unexpected'); fs.writeFile('./backup/package.json', data, function(err) { if (err) { console.error(err); } else { console.log('backup successed'); }}); }}); } else { fs.writeFile('./backup/package.json', data, function(err) { if (err) { console.error(err); } else { console.log('backup successed'); }}); }}); }});Copy the code

2. Anonymous debugging

Uncomment the above code to throw an exception before executing

WTF, which method did this unexpected error come from?

What? Do you think the code is beautifully written and impeccably elegant? So you can now ignore the following and go straight to the last comment

How to write a JS callback that looks elegant?

1. Eliminate callback nesting

2. Naming method

fs.readFile('./package.json', function(err, data) { if (err) { console.error(err); } else { writeFileContentToBackup(data); }}); function writeFileContentToBackup(fileContent) { checkBackupDir(function(err) { if (err) { console.error(err); } else { backup(fileContent, log); }}); } function checkBackupDir(cb) { fs.exists('./backup', function(exists) { if (! exists) { mkBackupDir(cb); } else { cb(null); }}); } function mkBackupDir(cb) { // throw new Error('unexpected'); fs.mkdir('./backup', cb); } function backup(data, cb) { fs.writeFile('./backup/package.json', data, cb); } function log(err) { if (err) { console.error(err); } else { console.log('backup successed'); }}Copy the code

We can now quickly locate the method that threw the exception

The stone of another mountain may attack jade

Optimize asynchronous code with third-party libraries

browser js

  • jQuery Deferred
    • ajax
    • animate

NodeJs

  • Async

    • async.each
    • async.map
    • async.waterfall
  • ECMAScript 6

    • Promise
    • Generator

jQuery Deferred

Introduced in jQUERy-1.5 and used in ajax, animate, and other asynchronous methods

A simple example:

function sleep(timeout) { var dtd = $.Deferred(); setTimeout(dtd.resolve, timeout); return dtd; Function sleep(timeout) {return $.deferred (function(DTD) {setTimeout(dtd.resolve, timeout); }); } console.time('sleep'); sleep(2000).done(function() { console.timeEnd('sleep'); });Copy the code

A complicated example:

function loadImg(src) { var dtd = $.Deferred(), img = new Image; img.onload = function() { dtd.resolve(img); } img.onerror = function(e) { dtd.reject(e); } img.src = src; return dtd; } loadImg('http://www.baidu.com/favicon.ico').then( function(img) { $('body').prepend(img); }, function() { alert('load error'); })Copy the code

So the question is, do I want to display baidu Logo after 5s?

Common writing:

sleep(5000).done(function() {  
     loadImg('http://www.baidu.com/favicon.ico').done(function(img) {
        $('body').prepend(img);
    });
});
Copy the code

Idiotic writing:

setTimeout(function() {  
    loadImg('http://www.baidu.com/favicon.ico').done(function(img) {
        $('body').prepend(img);
    });
}, 5000);
Copy the code

Literary writing (sleep 5s and load picture synchronous execution) :

$.when(sleep(5000), loadImg('http://www.baidu.com/favicon.ico')).done(function(ignore, img) {
    $('body').prepend(img);
});
Copy the code

Async

How to use: github.com/caolan/asyn…

Advantages:

  1. Simple and easy to understand
  2. Function rich, can satisfy almost any callback needs
  3. popular

Disadvantages:

  1. Introduce additional third-party libraries
  2. It’s simple, but it’s still hard to master all the apis

ECMAScript 6

The goal of ES6 is to make the JavaScript language usable for writing large and complex applications, making it an enterprise development language.

Next, I’ll show you how the new ES6 features, Promise objects and Generator functions, can make your code look more elegant.

For more ES6 features: Getting Started with ECMAScript 6

Promise

Initialization and use of the Promise object:

var promise = new Promise(function(resolve, reject) { setTimeout(function() { if (true) { resolve('ok'); } else { reject(new Error('unexpected error')); }}, 2000); }); promise.then(function(msg) { // throw new Error('unexpected resolve error'); console.log(msg); }).catch(function(err) { console.error(err); });Copy the code

The JavaScript Promise API treats any object that contains a then method as a “promise-like” (or thenable in the jargon)

Similar to the jQuery Deferred object described above, but not quite the same API methods, error catching, etc. You can use the following methods to convert:

var promise = Promise.resolve($.Deferred());  
Copy the code

So how do you rewrite the callback hell example using Promise?

Json readPackagefile. then(function(data) {// 2. Return checkBackupDir.then(function() {// 3. Return backupPackageFile(data); }); }).then(function() { console.log('backup successed'); }).catch(function(err) { console.error(err); });Copy the code

That simple?

Check the definitions of readPackageFile, checkBackupDir, and backupPackageFile:

var readPackageFile = new Promise(function(resolve, reject) {  
    fs.readFile('./package.json', function(err, data) {
        if (err) {
            reject(err);
        }

        resolve(data);
    });
});

var checkBackupDir = new Promise(function(resolve, reject) {  
    fs.exists('./backup', function(exists) {
        if (!exists) {
            resolve(mkBackupDir);
        } else {
            resolve();
        }
    });
});

var mkBackupDir = new Promise(function(resolve, reject) {  
    // throw new Error('unexpected error');
    fs.mkdir('./backup', function(err) {
        if (err) {
            return reject(err);
        }

        resolve();
    });
});

function backupPackageFile(data) {  
    return new Promise(function(resolve, reject) {
        fs.writeFile('./backup/package.json', data, function(err) {
            if (err) {
                return reject(err);
            }

            resolve();
        });
    });
};
Copy the code

Is not feel full deception, said good simple, don’t hit first, at least call up or very simple XD. Personally, one of the biggest benefits of using promises is that they make the calls feel good.

Process optimization, using the non-blocking nature of JS, we found that step 1 and Step 2 can be performed synchronously:

Promise.all([readPackageFile, checkBackupDir]).then(function(res) {  
    return backupPackageFile(res[0]);
}).then(function() {
    console.log('backup successed');
}).catch(function(err) {
    console.error(err);
});
Copy the code

Libraries available in an ES5 environment:

  • bluebird
  • Q
  • when
  • WinJS
  • RSVP.js

Generator

NodeJs does not support Generator writing by default, but after V0.12 you can add the –harmony argument to support it:

> node --harmony generator.js
Copy the code

Allows a function to exit as a return at a specific place, but to resume execution at that location and state later

Function * foo(input) {console.log(' this is done the first time the next method is called '); yield input; Console. log(' this will not be executed unless the next method is called again '); } var g = foo(10); console.log(Object.prototype.toString.call(g)); // [object Generator] console.log(g.next()); // { value: 10, done: false } console.log(g.next()); // { value: undefined, done: true }Copy the code

If this is difficult to understand, consider yield as a return statement, break the entire function into smaller pieces, execute one piece sequentially each time generator’s next method is called, and exit at yield.

To tell you a surprising secret, we can now “sync” js sleep:

var sleepGenerator; function sleep(time) { setTimeout(function() { sleepGenerator.next(); // step 5 }, time); } var sleepGenerator = (function * () { console.log('wait... '); // step 2 console.time('how long did I sleep'); // step 3 yield sleep(2000); // step 4 console.log('weakup'); // step 6 console.timeEnd('how long did I sleep'); // step 7 }()); sleepGenerator.next(); // step 1Copy the code

Combination, using Promise and Generator to rewrite the callback hell example

Preparation before fit, refer to Q.A. sync:

function run(makeGenerator) {  
    function continuer(verb, arg) {
        var result;
        try {
            result = generator[verb](arg);
        } catch (err) {
            return Promise.reject(err);
        }
        if (result.done) {
            return result.value;
        } else {
            return Promise.resolve(result.value).then(callback, errback);
        }
    }
    var generator = makeGenerator.apply(this, arguments);
    var callback = continuer.bind(continuer, "next");
    var errback = continuer.bind(continuer, "throw");
    return callback();
}
Copy the code

ReadPackageFile, checkBackupDir, and backupPackageFile use the above Promise definitions.

Execution after assembly:

run(function *() { try { // 1. Json var data = yield readPackageFile; Check whether the backup directory exists. If the backup directory does not exist, yield checkBackupDir. // 3. Yield backupPackageFile(data) to the backup file. console.log('backup successed'); } catch (err) { console.error(err); }});Copy the code

It’s like writing synchronous code.

conclusion

After reading this article, if you sigh: “Damn, JS can write like this”, then my purpose has been achieved. The purpose of this article is not to introduce the usage of Async, Deferred, Promise, and Generator. If you are not familiar with these concepts, you are advised to refer to other materials. Writing JS is like speaking English, not write in JS, but think in JS. Either way, the goal is to make your code more readable and maintainable; If you are making changes in an existing project, consider the aggressiveness of existing code.

How to Write ASYNCHRONOUS JAVASCRIPT Code gracefully (part 2)

Refer to the address

  • The callback hell
  • JavaScript Promise Apocalypse
  • Promises/A+
  • ECMAScript introduction to 6
  • JavaScript Promises
  • Use generators to solve JavaScript callback nesting problems
  • Hug the Generator and say goodbye to the callback

The picture is from:Forwardjs.com/img/worksho…