搜尋
首頁web前端js教程js中的流程控制:Callbacks&Promises&Async/Awai的分析

js中的流程控制:Callbacks&Promises&Async/Awai的分析

Aug 25, 2018 pm 03:40 PM
csshtmljavascriptnode.js

這篇文章帶給大家的內容是關於js中的流程控制:Callbacks&Promises&Async/Awai的分析,有一定的參考價值,有需要的朋友可以參考一下,希望對你有幫助。

JavaScript經常被聲稱是_非同步_。那是什麼意思?它如何影響發展?近年來這種方法有何變化?

請思考以下程式碼:

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

大多數語言都處理每一行同步。第一行運行並傳回結果。第二行在第一行完成後運行無論需要多長時間。

單執行緒處理

JavaScript在單一處理執行緒上執行。在瀏覽器標籤中執行時,其他所有內容都會停止,因為在並行線程上不會發生對頁面DOM的更改;將一個線程重定向到另一個URL而另一個線程嘗試追加子節點是危險的。

這對使用者來說是顯而易見。例如,JavaScript偵測到按鈕單擊,運行計算並更新DOM。完成後,瀏覽器可以自由處理佇列中的下一個項目。

(附註:其他語言如PHP也使用單一線程,但可以由多線程伺服器(如Apache)管理。同時對同一個PHP運行時頁面的兩個請求可以啟動兩個運行隔離的實例的線程。)

使用回調進行非同步

單執行緒引發了一個問題。當JavaScript呼叫「慢」進程(例如瀏覽器中的Ajax請求或伺服器上的資料庫操作)時會發生什麼?這個操作可能需要幾秒鐘 - 甚至幾分鐘。瀏覽器在等待回應時會被鎖定。在伺服器上,Node.js應用程式將無法進一步處理使用者請求。

解決方案是非同步處理。而不是等待完成,一個進程被告知在結果準備好時呼叫另一個函數。這稱為callback,它作為參數傳遞給任何非同步函數。例如:

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

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

doSomethingAsync()接受一個回呼函數作為參數(只傳遞對該函數的引用,因此幾乎沒有開銷)。 doSomethingAsync()需要多長時間並不重要;我們所知道的是callback1()將在未來的某個時刻執行。控制台將顯示:

finished
doSomethingAsync complete

回呼地獄

通常,回呼只能由一個非同步函數呼叫。因此可以使用簡潔的匿名內聯函數:

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

透過巢狀回呼函數,可以串列完成一系列兩個或多個非同步呼叫。例如:

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

不幸的是,這引入了回呼地獄 - 一個臭名昭著的概念(http://callbackhell.com/) !程式碼難以閱讀,並且在添加錯誤處理邏輯時會變得更糟。

回呼地獄在客戶端編碼中相對較少。如果您正在進行Ajax調用,更新DOM並等待動畫完成,它可以深入兩到三個級別,但它通常仍然可以管理。

作業系統或伺服器進程的情況不同。 Node.js API呼叫可以接收檔案上載,更新多個資料庫表,寫入日誌,並在發送回應之前進行進一步的API呼叫。

Promises

ES2015(ES6)推出了Promises。回調仍然可以使用,但Promises提供了更清晰的語法chains非同步命令,因此它們可以串行運行(更多相關內容)。

要啟用基於Promise的執行,必須變更基於非同步回呼的函數,以便它們立即傳回Promise物件。這個promises物件在未來的某個時刻運行兩個函數之一(作為參數傳遞):

  • resolve :處理成功完成時運行的回呼函數

  • reject :發生故障時執行的可選回呼函數。

在下面的範例中,資料庫API提供了一個接受回呼函數的connect()方法。外部asyncDBconnect()函數立即傳回一個新的Promise,並在建立連接或失敗後運行resolve()或reject():

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 提供了util.promisify()實用程序,將基於回呼的函數轉換為基於Promise的替代方法。有幾個條件:

  1. 將回呼作為最後一個參數傳遞給非同步函數

  2. 回呼函數必須指向一個錯誤,後面跟著一個值參數。

範例:

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

readFileAsync('file.txt');

各種客戶端程式庫也提供promisify選項,但您可以自行建立幾個:

// 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);

非同步鏈

任何傳回Promise的東西都可以啟動.then()方法中定義的一系列非同步函數呼叫。每個都傳遞了上一個解決方案的結果:

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);
  });

同步函數也可以在.then()區塊中執行。傳回的值將傳遞給下一個.then()(如果有)。

.catch()方法定義了在觸發任何先前拒絕時呼叫的函數。此時,不會再運行.then()方法。您可以在整個鏈中使用多個.catch()方法來捕獲不同的錯誤。

ES2018引入了一個.finally()方法,無論結果如何都運行任何最終邏輯 - 例如,清理,關閉資料庫連接等。目前僅支援Chrome和Firefox,但技術委員會39已發布了 .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技巧

以上是js中的流程控制:Callbacks&Promises&Async/Awai的分析的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
Python vs. JavaScript:開發人員的比較分析Python vs. JavaScript:開發人員的比較分析May 09, 2025 am 12:22 AM

Python和JavaScript的主要區別在於類型系統和應用場景。 1.Python使用動態類型,適合科學計算和數據分析。 2.JavaScript採用弱類型,廣泛用於前端和全棧開發。兩者在異步編程和性能優化上各有優勢,選擇時應根據項目需求決定。

Python vs. JavaScript:選擇合適的工具Python vs. JavaScript:選擇合適的工具May 08, 2025 am 12:10 AM

選擇Python還是JavaScript取決於項目類型:1)數據科學和自動化任務選擇Python;2)前端和全棧開發選擇JavaScript。 Python因其在數據處理和自動化方面的強大庫而備受青睞,而JavaScript則因其在網頁交互和全棧開發中的優勢而不可或缺。

Python和JavaScript:了解每個的優勢Python和JavaScript:了解每個的優勢May 06, 2025 am 12:15 AM

Python和JavaScript各有優勢,選擇取決於項目需求和個人偏好。 1.Python易學,語法簡潔,適用於數據科學和後端開發,但執行速度較慢。 2.JavaScript在前端開發中無處不在,異步編程能力強,Node.js使其適用於全棧開發,但語法可能複雜且易出錯。

JavaScript的核心:它是在C還是C上構建的?JavaScript的核心:它是在C還是C上構建的?May 05, 2025 am 12:07 AM

javascriptisnotbuiltoncorc; sanInterpretedlanguagethatrunsonenginesoftenwritteninc.1)JavascriptwasdesignedAsignedAsalightWeight,drackendedlanguageforwebbrowsers.2)Enginesevolvedfromsimpleterterpretpretpretpretpreterterpretpretpretpretpretpretpretpretpretcompilerers,典型地,替代品。

JavaScript應用程序:從前端到後端JavaScript應用程序:從前端到後端May 04, 2025 am 12:12 AM

JavaScript可用於前端和後端開發。前端通過DOM操作增強用戶體驗,後端通過Node.js處理服務器任務。 1.前端示例:改變網頁文本內容。 2.後端示例:創建Node.js服務器。

Python vs. JavaScript:您應該學到哪種語言?Python vs. JavaScript:您應該學到哪種語言?May 03, 2025 am 12:10 AM

選擇Python還是JavaScript應基於職業發展、學習曲線和生態系統:1)職業發展:Python適合數據科學和後端開發,JavaScript適合前端和全棧開發。 2)學習曲線:Python語法簡潔,適合初學者;JavaScript語法靈活。 3)生態系統:Python有豐富的科學計算庫,JavaScript有強大的前端框架。

JavaScript框架:為現代網絡開發提供動力JavaScript框架:為現代網絡開發提供動力May 02, 2025 am 12:04 AM

JavaScript框架的強大之處在於簡化開發、提升用戶體驗和應用性能。選擇框架時應考慮:1.項目規模和復雜度,2.團隊經驗,3.生態系統和社區支持。

JavaScript,C和瀏覽器之間的關係JavaScript,C和瀏覽器之間的關係May 01, 2025 am 12:06 AM

引言我知道你可能會覺得奇怪,JavaScript、C 和瀏覽器之間到底有什麼關係?它們之間看似毫無關聯,但實際上,它們在現代網絡開發中扮演著非常重要的角色。今天我們就來深入探討一下這三者之間的緊密聯繫。通過這篇文章,你將了解到JavaScript如何在瀏覽器中運行,C 在瀏覽器引擎中的作用,以及它們如何共同推動網頁的渲染和交互。 JavaScript與瀏覽器的關係我們都知道,JavaScript是前端開發的核心語言,它直接在瀏覽器中運行,讓網頁變得生動有趣。你是否曾經想過,為什麼JavaScr

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

Safe Exam Browser

Safe Exam Browser

Safe Exam Browser是一個安全的瀏覽器環境,安全地進行線上考試。該軟體將任何電腦變成一個安全的工作站。它控制對任何實用工具的訪問,並防止學生使用未經授權的資源。

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

強大的PHP整合開發環境

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

VSCode Windows 64位元 下載

VSCode Windows 64位元 下載

微軟推出的免費、功能強大的一款IDE編輯器

MinGW - Minimalist GNU for Windows

MinGW - Minimalist GNU for Windows

這個專案正在遷移到osdn.net/projects/mingw的過程中,你可以繼續在那裡關注我們。 MinGW:GNU編譯器集合(GCC)的本機Windows移植版本,可自由分發的導入函式庫和用於建置本機Windows應用程式的頭檔;包括對MSVC執行時間的擴展,以支援C99功能。 MinGW的所有軟體都可以在64位元Windows平台上運作。