Callback problems in the javascript study guide

  • 2021-02-17 06:17:05
  • OfStack

The callback hell

For JavaScript programmers, dealing with callbacks is common, but dealing with callbacks that are too deep is not so good. The following code snippet uses three levels of callbacks, followed by one more level of scenarios, which is pretty tingly good. This is the classic callback hell.


getDirectories(function(dirs) {
  getFiles(dirs[0], function(files) {
    getContent(files[0], function(file, content) {
      console.log('filename:', file);
      console.log(content);
    });
  });
});
 
function getDirectories(callback) {
 setTimeout(function() {
  callback(['/home/ben']);
 }, 1000);
}
 
function getFiles(dir, callback) {
  setTimeout(function() {
    callback([dir + '/test1.txt', dir + '/test2.txt']);
  }, 1000)
}
 
function getContent(file, callback) {
  setTimeout(function() {
    callback(file, 'content');
  }, 1000)
}

The solution

There are many asynchronous solutions in the ecosystem to deal with callback hell, such as bluebird, Q, etc. This article focuses on the support for asynchronous programming in the ECMAScript 6/7 specification.

ES6 Promise

Promise is an asynchronous programming solution that is a great solution to the callback hell problem.

Promise gained mainstream acceptance in the JavaScript ecosystem in 2007 when the Dojo framework added the functionality of dojo.Deferred. With the popularity of dojo.Deferred, the CommonJS Promises/A specification was proposed in 2009 Kris Zyp. Subsequently, a large number of Promise implementations appeared in the ecosystem, including Q.js, FuturesJS and so on. Of course, the popularity of Promise is largely due to the existence of jQuery, but jQuery does not fully comply with CommonJS Promises/A specifications. As you can see later, the Promise is included in the ES 6 specification.
The Promise is described as follows:

The Promise object is a proxy for a return value that is not necessarily known at the time the promise object was created. It allows you to specify handling for the success or failure of an asynchronous operation. This allows asynchronous methods to return values just like synchronous methods: asynchronous methods will return 1 containing the original return value
The following code is implemented using ES53en for the example in "Callback Hell" section 1. The code doesn't look very clean either, but it is a significant improvement over traditional hierarchical callbacks, and it is more maintainable and readable.


getDirectories().then(function(dirs) {
  return getFiles(dirs[0]);
}).then(function(files) {
  return getContent(files[0]);
}).then(function(val) {
  console.log('filename:', val.file);
  console.log(val.content);
});
 
function getDirectories() {
  return new Promise(function (resolve, reject) {
    setTimeout(function() {
    resolve(['/home/ben']);
   }, 1000);
  });
}
 
function getFiles(dir) {
  return new Promise(function (resolve, reject) {
    setTimeout(function() {
      resolve([dir + '/test1.txt', dir + '/test2.txt']);
    }, 1000);
  });
}
 
function getContent(file) {
  return new Promise(function (resolve, reject) {
    setTimeout(function() {
      resolve({file: file, content: 'content'});
    }, 1000);
  });
}

ES6 Generator

The way Promise is implemented is not simple enough, we need better alternatives, and co is one of them. co is an asynchronous flow controller based on Generator (generator). It is necessary to understand Generator before understanding co. For those of you familiar with C#, the 2.0 version of C# introduces the yield keyword for iterative generators. ES 6 Generator is similar to C# in that it also uses the yield syntax sugar and implements the state machine internally. For specific usage, please refer to MDN document function* 1 section. For principle, please refer to AlloyTeam team Blog for in-depth understanding of Generator. Use co to skillfully combine ES6 Generator and ES6 Promise to make asynchronous calls more harmonious.


co(function* (){
  var dirs = yield getDirectories();
  var files = yield getFiles(dirs[0]);
  var contentVal = yield getContent(files[0]);
  console.log('filename:', contentVal.file);
  console.log(contentVal.content);
});

ES86en is very clever, its core code can be simplified to the following example, the general idea is to use recursive traversal generator until the state is complete, of course, ES87en does more.


runGenerator();
 
function* run(){
  var dirs = yield getDirectories();
  var files = yield getFiles(dirs[0]);
  var contentVal = yield getContent(files[0]);
  console.log('filename:', contentVal.file);
  console.log(contentVal.content);
}
 
function runGenerator(){
  var gen = run();
 
  function go(result){
    if(result.done) return;
    result.value.then(function(r){
      go(gen.next(r));
    });
  }
 
  go(gen.next());
}

ES7 Async/Await

ES6 Generator is really good, but it needs the support of the third party library. The good news is that ES 7 will introduce the Async/Await keyword to solve the asynchronous call problem perfectly. Well,.net is one step ahead,.net and framework 4.5 are already supported.
The future code would look like this:


run();
async function run() {
  var dirs = await getDirectories();
  var files = await getFiles(dirs[0]);
  var contentVal = await getContent(files[0]);
  console.log('filename:', contentVal.file);
  console.log(contentVal.content);
}

conclusion

From the classic callback asynchronous programming, to the ES6 Promise specification on the improvement of asynchronous programming, to co combined with ES Generator elegant processing, finally ES7 async/await perfect conclusion, we can understand why ECMAScript will appear these features and solve what problems, more clearly see the context of the development of JavaScript asynchronous programming.


Related articles: