Home  >  Article  >  Web Front-end  >  Detailed explanation of the usage of async in javascript

Detailed explanation of the usage of async in javascript

巴扎黑
巴扎黑Original
2017-08-23 14:00:153765browse

This article mainly introduces the usage of understanding javascript async. The editor thinks it is quite good. Now I will share it with you and give it as a reference. Let’s follow the editor to take a look

Written in front

This article will implement an optimal method for sequentially reading files. The implementation method starts from the oldest The callback method to the current async, I will also share with you my understanding of the thunk library and the co library. The effect achieved: read a.txt and b.txt sequentially, and concatenate the read contents into a string.

Synchronous reading


const readTwoFile = () => {
  const f1 = fs.readFileSync('./a.txt'),
    f2 = fs.readFileSync('./b.txt');
  return Buffer.concat([f1, f2]).toString();
};

This method is the most conducive to our understanding, the code is also very clear, without too much The nesting is very easy to maintain, but this has the biggest problem, that is, performance. What node advocates is asynchronous I/O to handle intensive I/O, and synchronous reading is wasteful to a large extent. Server CPU, the disadvantages of this method obviously outweigh the advantages, so just pass it. (In fact, the goal of any asynchronous programming solution in node is to achieve synchronous semantics and asynchronous execution.)

Use callbacks to read


const readTwoFile = () => {
  let str = null;
  fs.readFile('./a.txt', (err, data) => {
    if (err) throw new Error(err);
    str = data;
    fs.readFile('./b.txt', (err, data) => {
      if (err) throw new Error(err);
      str = Buffer.concat([str, data]).toString();
    });
  });
};

Using the callback method, it is very simple to implement. Just nest it directly. However, in this case, it is easy to cause a situation that is difficult to maintain and difficult to understand. The most extreme The situation is callback hell.

Promise implementation


const readFile = file => 
  new Promise((reslove, reject) => {
    fs.readFile(file, (err, data) => {
      if (err) reject(err);
      reslove(data);
    });
  });
const readTwoFile = () => {
  let bf = null;
  readFile('./a.txt')
    .then(
      data => {
        bf = data;
        return readFile('./b.txt');
      }, 
      err => { throw new Error(err) }
    )
    .then(
      data => {
        console.log(Buffer.concat([bf, data]).toString())
      }, 
      err => { throw new Error(err) }
    );
};

Promise can convert horizontal growth callbacks into vertical growth, which can solve some problems. But the problem caused by Promise is code redundancy. At first glance, it is all then, which is not very comfortable, but compared with callback function nesting, it has been greatly improved.

yield

Generator is found in many languages. It is essentially a coroutine. Let’s take a look at the differences and connections between coroutines, threads, and processes. :

  • Process: The basic unit of resource allocation in the operating system

  • Thread: The basic unit of resource scheduling in the operating system

  • Coroutine: an execution unit smaller than a thread, with its own CPU context, one coroutine and one stack

There may be multiple threads in a process. There may be multiple coroutines in a thread. The switching of processes and threads is controlled by the operating system, while the switching of coroutines is controlled by the programmer himself. Asynchronous I/O uses callbacks to deal with intensive I/O. You can also use coroutines to deal with it. Switching coroutines does not waste a lot of resources. Write an I/O operation into a coroutine, and proceed like this During I/O, you can give up the CPU to other coroutines.

js also supports coroutines, which is yield. The intuitive feeling that using yield gives us is that the execution stops at this place and other code continues to run. When you want it to continue execution, it will continue to execute.


function *readTwoFile() {
  const f1 = yield readFile('./a.txt');
  const f2 = yield readFile('./b.txt'); 
  return Buffer.concat([f1, f2]).toString();
}

The sequential reading under yield is also a sequential reading method. There are two different implementation methods for readFile,

Use thunkify


const thunkify = (fn, ctx) => (...items) => (done) => {
  ctx = ctx || null;
  let called = false;
  items.push((...args) => {
    if (called) return void 0;
    called = true;
    done.apply(ctx, args);
  });
  try {
    fn.apply(ctx, items);  
  } catch(err) {
    done(err);
  }
};

The thunkify function is a kind of currying idea. The last parameter passed in is the callback function. It can be easily done using thunkify. Implement the automated process of yield function:


const run = fn => {
  const gen = fn();
  let res;
  (function next(err, data) {
    let g = gen.next(data);
    if (g.done) return void 0;
    g.value(next);
  })();
};

Use Promise


##

const readFile = file => 
  new Promise((reslove, reject) => {
    fs.readFile(file, (err, data) => {
      if (err) reject(err);
      reslove(data);
    });
  });
const run = fn => {
  const gen = fn();
  let str = null;
  (function next(err, data) {
    let res = gen.next(data);
    if (res.done) return void 0;
    res.value.then(
      data => {
        next(null, data);
      }, 
      err => { throw new Error(err); }
    );
  })();
};
run(readTwoFile);

Both of the above methods are available To achieve the process of automatically executing yield, is there a way that is compatible with these two implementation methods? Master TJ has given another library, which is the co library. Let’s take a look at the usage first:


// readTwoFile的实现与上面类似,readFile既可以利用Promise也可以利用thunkify
// co库返回一个Promise对象
co(readTwoFile).then(data => console.log(data));

Let’s take a look at the implementation of the co library. The co library will return a Promise object by default. For the value after yield (such as res.value above), the co library will convert it into a Promise. The implementation idea is very simple, basically using recursion. The general idea is as follows:


const baseHandle = handle => res => {
  let ret;
  try {
    ret = gen[handle](res);
  } catch(e) {
    reject(e);
  }
  next(ret);
};
function co(gen) {
  const ctx = this,
    args = Array.prototype.slice.call(arguments, 1);
  return new Promise((reslove, reject) => {
    if (typeof gen === 'function') gen = gen.apply(ctx, args);
    if (!gen || typeof gen.next !== 'function') return resolve(gen);

    const onFulfilled = baseHandle('next'),
      onRejected = baseHandle('throw');

    onFulfilled();

    function next(ret) {
      if (ret.done) reslove(ret.value);
      // 将yield的返回值转换为Proimse
      const value = toPromise.call(ctx, ret.value);
      if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
      return onRejected(new TypeError('yield type error'));
    }
  });
}

toPromise is to convert some types into Promise. From here we can see that Which types can be placed behind yield, here is a commonly used one:


// 把thunkify之后的函数转化为Promise的形式
function thunkToPromise(fn) {
  const ctx = this;
  return new Promise(function (resolve, reject) {
    fn.call(ctx, function (err, res) {
      if (err) return reject(err);
      if (arguments.length > 2) res = slice.call(arguments, 1);
      resolve(res);
    });
  });
}

Recently, Node has supported async/await, which can be used to perform asynchronous operations:

Ultimate Solution


const readFile = file => 
  new Promise((reslove, reject) => {
    fs.readFile(file, (err, data) => {
      if (err) reject(err);
      reslove(data);
    });
  });
const readTwoFile = async function() {
  const f1 = await readFile('./a.txt');
  const f2 = await readFile('./b.txt');  
  return Buffer.concat([f1, f2]).toString();
};
readTwoFile().then(data => {
  console.log(data);
});

What async/await does is to connect Promise objects in series to avoid then The calling method, the code is very easy to read, and it is a synchronous method. You no longer need to rely on other external class libraries (such as co libraries) to elegantly solve the callback problem


The above is the detailed content of Detailed explanation of the usage of async in javascript. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn