Home  >  Article  >  Web Front-end  >  Process control in js: Analysis of Callbacks&Promises&Async/Awai

Process control in js: Analysis of Callbacks&Promises&Async/Awai

不言
不言Original
2018-08-25 15:40:551241browse

The content of this article is about process control in js: the analysis of Callbacks&Promises&Async/Awai. It has certain reference value. Friends in need can refer to it. I hope it will be helpful to you.

JavaScript often claims to be _asynchronous_. what does that mean? How does it affect development? How has this approach changed in recent years?

Consider the following code:

result1 = doSomething1();
result2 = doSomething2(result1);

Most languages ​​handle each line synchronously. The first line runs and returns the result. The second line runs after the first line completes no matter how long it takes.

Single-threaded processing

JavaScript runs on a single processing thread. While executing in a browser tab, everything else stops because no changes to the page's DOM occur on parallel threads; it's dangerous to redirect one thread to another URL while another thread tries to append child nodes.

This is obvious to the user. For example, JavaScript detects button clicks, runs calculations and updates the DOM. Once completed, the browser is free to process the next item in the queue.

(Side note: other languages ​​like PHP also use a single thread, but can be managed by a multi-threaded server like Apache. Two simultaneous requests to the same PHP runtime page can start two runs isolated The thread of the instance.)

Using callbacks for asynchronous

Single threading raises a problem. What happens when JavaScript calls a "slow" process (such as an Ajax request in the browser or a database operation on the server)? This operation may take several seconds - or even minutes. The browser is locked while waiting for a response. On the server, the Node.js application will be unable to further process the user request.

The solution is asynchronous processing. Instead of waiting for completion, a process is told to call another function when the result is ready. This is called a callback and is passed as a parameter to any asynchronous function. For example:

doSomethingAsync(callback1);
console.log('finished');

// call when doSomethingAsync completes
function callback1(error) {
  if (!error) console.log('doSomethingAsync complete');
}

doSomethingAsync() accepts a callback function as argument (only a reference to the function is passed, so there is almost no overhead). It doesn't matter how long doSomethingAsync() takes; all we know is that callback1() will execute at some point in the future. The console will display:

finished
doSomethingAsync complete

Callback Hell

Typically, callbacks can only be called by an asynchronous function. Therefore, you can use concise anonymous inline functions:

doSomethingAsync(error => {
  if (!error) console.log('doSomethingAsync complete');
});

By nesting callback functions, a series of two or more asynchronous calls can be completed in series. For example:

async1((err, res) => {
  if (!err) async2(res, (err, res) => {
    if (!err) async3(res, (err, res) => {
      console.log('async1, async2, async3 complete.');
    });
  });
});

Unfortunately, this introduces callback hell - a notorious concept (http://callbackhell.com/)! The code is difficult to read and gets worse when error handling logic is added.

Callback hell is relatively rare in client-side coding. If you're making an Ajax call, updating the DOM and waiting for the animation to complete, it can go two or three levels deep, but it's usually still manageable.

The situation is different for the operating system or server process. Node.js API calls can receive file uploads, update multiple database tables, write to logs, and make further API calls before sending a response.

Promises

ES2015 (ES6) introduced Promises. Callbacks can still be used, but Promises provide a cleaner syntax for chaining asynchronous commands so they can be run serially (more on this).

To enable Promise-based execution, asynchronous callback-based functions must be changed so that they return Promise objects immediately. This promises object runs one of two functions (passed as arguments) at some point in the future:

  • resolve: a callback function that runs when the handler completes successfully

  • reject: Optional callback function to run when a failure occurs.

In the example below, the database API provides a connect() method that accepts a callback function. The external asyncDBconnect() function returns a new Promise immediately and runs resolve() or reject() after the connection is established or fails:

const db = require('database');

// connect to database
function asyncDBconnect(param) {

  return new Promise((resolve, reject) => {

    db.connect(param, (err, connection) => {
      if (err) reject(err);
      else resolve(connection);
    });

  });

}

Node.js 8.0 provides the util.promisify() utility that will Callback-based functions are converted to Promise-based alternatives. There are several conditions:

  1. Pass the callback as the last argument to the async function

  2. The callback function must point to an error, followed by a value argument .

Example:

// Node.js: promisify fs.readFile
const
  util = require('util'),
  fs = require('fs'),
  readFileAsync = util.promisify(fs.readFile);

readFileAsync('file.txt');

Various client libraries also provide promisify options, but you can create a few of your own:

// promisify a callback function passed as the last parameter
// the callback function must accept (err, data) parameters
function promisify(fn) {
  return function() {
      return new Promise(
        (resolve, reject) => fn(
          ...Array.from(arguments),
        (err, data) => err ? reject(err) : resolve(data)
      )
    );
  }
}

// example
function wait(time, callback) {
  setTimeout(() => { callback(null, 'done'); }, time);
}

const asyncWait = promisify(wait);

ayscWait(1000);

Async chain

Anything that returns a Promise can initiate a series of asynchronous function calls defined in the .then() method. Each is passed the result of the previous solution:

asyncDBconnect('http://localhost:1234')
  .then(asyncGetSession)      // passed result of asyncDBconnect
  .then(asyncGetUser)         // passed result of asyncGetSession
  .then(asyncLogAccess)       // passed result of asyncGetUser
  .then(result => {           // non-asynchronous function
    console.log('complete');  //   (passed result of asyncLogAccess)
    return result;            //   (result passed to next .then())
  })
  .catch(err => {             // called on any reject
    console.log('error', err);
  });

Synchronous functions can also be executed in a .then() block. The returned value will be passed to the next .then() if any. The

.catch() method defines the function that is called when any previous rejection is triggered. At this point, the .then() method will no longer be run. You can use multiple .catch() methods throughout the chain to catch different errors.

ES2018 introduced a .finally() method to run any final logic regardless of the outcome - for example, cleanup, closing database connections, etc. Currently only Chrome and Firefox are supported, but Technical Committee 39 has released the .finally() polyfill.

function doSomething() {
  doSomething1()
  .then(doSomething2)
  .then(doSomething3)
  .catch(err => {
    console.log(err);
  })
  .finally(() => {
    // tidy-up here!
  });
}

使用Promise.all()进行多个异步调用

Promise .then()方法一个接一个地运行异步函数。如果顺序无关紧要 - 例如,初始化不相关的组件 - 同时启动所有异步函数并在最后(最慢)函数运行解析时结束更快。

这可以通过Promise.all()来实现。它接受一组函数并返回另一个Promise。例如:

Promise.all([ async1, async2, async3 ])
  .then(values => {           // array of resolved values
    console.log(values);      // (in same order as function array)
    return values;
  })
  .catch(err => {             // called on any reject
    console.log('error', err);
  });

如果任何一个异步函数调用失败,则Promise.all()立即终止。

使用Promise.race的多个异步调用()

Promise.race()与Promise.all()类似,只是它会在first Promise解析或拒绝后立即解析或拒绝。只有最快的基于Promise的异步函数才能完成:

Promise.race([ async1, async2, async3 ])
  .then(value => {            // single value
    console.log(value);
    return value;
  })
  .catch(err => {             // called on any reject
    console.log('error', err);
  });

但是有什么别的问题吗?

Promises 减少了回调地狱但引入了别的问题。

教程经常没有提到_整个Promise链是异步的。使用一系列promise的任何函数都应返回自己的Promise或在最终的.then(),. catch()或.finally()方法中运行回调函数。

学习基础知识至关重要。

Async/Await

Promises 可能令人生畏,因此ES2017引入了async and await。 虽然它可能只是语法糖,它使Promise更完善,你可以完全避免.then()链。 考虑下面的基于Promise的示例:

function connect() {

  return new Promise((resolve, reject) => {

    asyncDBconnect('http://localhost:1234')
      .then(asyncGetSession)
      .then(asyncGetUser)
      .then(asyncLogAccess)
      .then(result => resolve(result))
      .catch(err => reject(err))

  });
}

// run connect (self-executing function)
(() => {
  connect();
    .then(result => console.log(result))
    .catch(err => console.log(err))
})();

用这个重写一下async/await:

  • 外部函数必须以async语句开头

  • 对异步的基于Promise的函数的调用必须在await之前,以确保在下一个命令执行之前完成处理。

async function connect() {

  try {
    const
      connection = await asyncDBconnect('http://localhost:1234'),
      session = await asyncGetSession(connection),
      user = await asyncGetUser(session),
      log = await asyncLogAccess(user);

    return log;
  }
  catch (e) {
    console.log('error', err);
    return null;
  }

}

// run connect (self-executing async function)
(async () => { await connect(); })();

await有效地使每个调用看起来好像是同步的,而不是阻止JavaScript的单个处理线程。 此外,异步函数总是返回一个Promise,因此它们可以被其他异步函数调用。

async/await 代码可能不会更短,但有相当大的好处:

1、语法更清晰。括号更少,错误更少。

2、调试更容易。可以在任何await语句上设置断点。
3、错误处理更好。try / catch块可以与同步代码一样使用。

4、支持很好。它在所有浏览器(IE和Opera Mini除外)和Node 7.6+中都得到了支持。

但是并非所有都是完美的......

切勿滥用async/await

async / await仍然依赖于Promises,它最终依赖于回调。你需要了解Promises是如何工作的,并且没有Promise.all()和Promise.race()的直接等价物。并且不要忘记Promise.all(),它比使用一系列不相关的await命令更有效。

同步循环中的异步等待

在某些时候,您将尝试调用异步函数中的同步循环。例如:

async function process(array) {
  for (let i of array) {
    await doSomething(i);
  }
}

它不会起作用。这也不会:

async function process(array) {
  array.forEach(async i => {
    await doSomething(i);
  });
}

循环本身保持同步,并且总是在它们的内部异步操作之前完成。

ES2018引入了异步迭代器,它与常规迭代器一样,但next()方法返回Promise。因此,await关键字可以与for循环一起用于串行运行异步操作。例如:

async function process(array) {
  for await (let i of array) {
    doSomething(i);
  }
}

但是,在实现异步迭代器之前,最好将数组项映射到异步函数并使用Promise.all()运行它们。例如:

const
  todo = ['a', 'b', 'c'],
  alltodo = todo.map(async (v, i) => {
    console.log('iteration', i);
    await processSomething(v);
});

await Promise.all(alltodo);

这具有并行运行任务的好处,但是不可能将一次迭代的结果传递给另一次迭代,并且映射大型数组可能在性能消耗上是很昂贵。

try/catch 有哪些问题了?

如果省略任何await失败的try / catch,async函数将以静默方式退出。如果您有一组很长的异步await命令,则可能需要多个try / catch块。

一种替代方案是高阶函数,它捕获错误,因此try / catch块变得不必要(thanks to @wesbos for the suggestion):

async function connect() {

  const
    connection = await asyncDBconnect('http://localhost:1234'),
    session = await asyncGetSession(connection),
    user = await asyncGetUser(session),
    log = await asyncLogAccess(user);

  return true;
}

// higher-order function to catch errors
function catchErrors(fn) {
  return function (...args) {
    return fn(...args).catch(err => {
      console.log('ERROR', err);
    });
  }
}

(async () => {
  await catchErrors(connect)();
})();

但是,在应用程序必须以与其他错误不同的方式对某些错误做出反应的情况下,此选项可能不实用。

尽管有一些陷阱,async / await是JavaScript的一个优雅补充。

JavaScript 旅程

异步编程是一项在JavaScript中无法避免的挑战。回调在大多数应用程序中都是必不可少的,但它很容易陷入深层嵌套的函数中。

Promises 抽象回调,但有许多语法陷阱。 转换现有函数可能是一件苦差事,而.then()链仍然看起来很混乱。

幸运的是,async / await提供了清晰度。代码看起来是同步的,但它不能独占单个处理线程。它将改变你编写JavaScript的方式!

相关推荐:

整理Javascript流程控制语句学习笔记_javascript技巧

JavaScript中使用Callback控制流程介绍_javascript技巧

The above is the detailed content of Process control in js: Analysis of Callbacks&Promises&Async/Awai. 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