Heim  >  Artikel  >  Web-Frontend  >  Vereinfachen Sie asynchronen Code mit Async-Funktionen (JavaScript-Entwicklungstipps)

Vereinfachen Sie asynchronen Code mit Async-Funktionen (JavaScript-Entwicklungstipps)

PHPz
PHPzOriginal
2017-04-23 09:49:581205Durchsuche

Promises wurden im Internet populär, als sie zum ersten Mal auf JavaScript veröffentlicht wurden – sie helfen Entwicklern, der Callback-Hölle zu entkommen und die Asynchronitätsprobleme zu lösen, die JavaScript-Entwickler vielerorts plagen. Aber Versprechen sind alles andere als perfekt. Sie bitten ständig um Rückrufe, können aber bei manchen komplexen Themen immer noch etwas chaotisch und unglaublich überflüssig sein.

Mit der Einführung von ES6 (jetzt ES2015 genannt) verfügen wir zusätzlich zur Einführung der Promise-Spezifikationen und der Notwendigkeit, diese unzähligen Bibliotheken anzufordern, auch über Generatoren. Generatoren können die Ausführung innerhalb einer -Funktion stoppen, was bedeutet, dass sie in eine Mehrzweckfunktion eingeschlossen werden können und wir warten können, bis der asynchrone Vorgang abgeschlossen ist, bevor wir mit der nächsten Codezeile fortfahren. Plötzlich sieht Ihr asynchroner Code möglicherweise synchron aus.

Dies ist nur der erste Schritt. Asynchrone Funktionen wurden seit ihrer Einführung in ES2017 in diesem Jahr standardisiert und die lokale Unterstützung weiter optimiert. Die Idee von asynchronen Funktionen besteht darin, Generatoren für die asynchrone Programmierung zu verwenden und ihnen eine eigene Semantik und Syntax zu verleihen. Daher müssen Sie keine Bibliothek verwenden, um die gekapselten Dienstprogrammfunktionen abzurufen, da diese im Hintergrund verwaltet werden.

Um das Async/Await-Beispiel im Artikel auszuführen, benötigen Sie einen kompatiblen Browser.

Lauffähig

Auf der Clientseite bieten Chrome, Firefox und Opera eine gute Unterstützung für asynchrone Funktionen.

Vereinfachen Sie asynchronen Code mit Async-Funktionen (JavaScript-Entwicklungstipps)

Ab Version 7.6 aktiviert Node.js standardmäßig Async/Await.

Vergleich zwischen asynchronen Funktionen und Generatoren

Hier ist ein Beispiel für die Verwendung von Generatoren für die asynchrone Programmierung unter Verwendung der Q-Bibliothek:

var doAsyncOp = Q.async(function* () { 
 
  var val = yield asynchronousOperation(); 
 
  console.log(val); 
 
  return val; 
 
});

Q .async ist eine Wrapper-Funktion, die Dinge nach der Szene verarbeitet. Dabei stellt * die Funktion als Generatorfunktion dar, yield stellt die Stoppfunktion dar und wird durch eine Wrapper-Funktion ersetzt. Q.async gibt eine Funktion zurück, der Sie wie doAsyncOp einen Wert zuweisen und diese dann aufrufen können.

Die neue Syntax in ES7 ist prägnanter:

async function doAsyncOp () { 
 
  var val = await asynchronousOperation();      
 
  console.log(val); 
 
  return val; 
 
};

Es gibt keinen großen Unterschied > eine gekapselte Funktion und *-Symbole verwenden Sie stattdessen das Schlüsselwort async. Das Schlüsselwort yield wurde ebenfalls durch „await“ ersetzt. Diese beiden Beispiele machen tatsächlich dasselbe: Weisen Sie nach Abschluss der asynchronen Operation den Wert val zu, geben Sie dann das Ergebnis aus und geben Sie es zurück. Promises in asynchrone Funktionen umwandeln

Wie würde das vorherige Beispiel aussehen, wenn wir Vanilla Promises verwenden würden?

function doAsyncOp () { 
 
  return asynchronousOperation().then(function(val) { 
 
    console.log(val); 
 
    return val; 
 
  }); 
 
};

Hier ist die gleiche Zahl von Codezeilen, aber das liegt daran, dass dann und die an sie übergebene

Rückruffunktion

viel zusätzlichen Code hinzufügt. Ein weiteres Ärgernis sind die beiden Return-Schlüsselwörter. Das hat mich schon immer gestört, denn es ist schwer herauszufinden, was genau eine Funktion, die Versprechen verwendet, zurückgibt. Wie Sie sehen können, gibt diese Funktion ein Versprechen zurück, das val zugewiesen wird. Ratet mal, was auch immer Sie in dieser Funktion zurückgeben, Sie geben heimlich ein Versprechen zurück, das dies auflöst Wert. Wenn Sie überhaupt keinen Wert zurückgeben, wird das von Ihnen zurückgegebene Versprechen implizit in undefiniert aufgelöst.

Verkettete Operationen

Einer der Gründe, warum Promise beliebt ist, ist, dass es mehrere asynchrone Operationen in einem verketteten Aufruf verbinden kann, wodurch eingebettete Rückrufe vermieden werden. Aber asynchrone Funktionen sind in dieser Hinsicht sogar noch besser als Promise.

Im Folgenden wird gezeigt, wie Promise zum Ausführen von Kettenoperationen verwendet wird (zur Demonstration führen wir einfach asynchronousOperation mehrmals aus).

function doAsyncOp() { 
 
  return asynchronousOperation() 
 
    .then(function(val) { 
 
      return asynchronousOperation(val); 
 
    }) 
 
    .then(function(val) { 
 
      return asynchronousOperation(val); 
 
    }) 
 
    .then(function(val) { 
 
      return asynchronousOperation(val); 
 
    }); 
 
}

Mit der asynchronen Funktion müssen Sie nur asynchronousOperation wie beim Schreiben von synchronem Code aufrufen:

async function doAsyncOp () { 
 
  var val = await asynchronousOperation(); 
 
  val = await asynchronousOperation(val); 
 
  val = await asynchronousOperation(val); 
 
  return await asynchronousOperation(val); 
 
};

Auch im Finale return-Anweisung Es besteht keine Notwendigkeit, „await“ zu verwenden, denn unabhängig davon, ob es verwendet wird oder nicht, gibt es ein Promise zurück, das einen bearbeitbaren Endwert enthält.

Gleichzeitige Vorgänge

Promise hat eine weitere großartige Funktion: Sie können mehrere asynchrone Vorgänge gleichzeitig ausführen und warten, bis alle abgeschlossen sind, bevor sie mit

Andere

fortfahren Veranstaltung . Die ES2015-Spezifikation stellt Promise.all() bereit, das hierfür verwendet wird. Hier ist ein Beispiel:

function doAsyncOp() { 
 
  return Promise.all([ 
 
    asynchronousOperation(), 
 
    asynchronousOperation() 
 
  ]).then(function(vals) { 
 
    vals.forEach(console.log); 
 
    return vals; 
 
  }); 
 
}

Promise.all() kann auch als asynchrone Funktion verwendet werden:

async function doAsyncOp() { 
 
  var vals = await Promise.all([ 
 
    asynchronousOperation(), 
 
    asynchronousOperation() 
 
  ]); 
 
  vals.forEach(console.log.bind(console)); 
 
  return vals; 
 
}

Auch wenn hier Promise.all verwendet wird, ist der Code immer noch sehr klar.

Umgang mit Ablehnungen

Promises 可以被接受(resovled)也可以被拒绝(rejected)。被拒绝的 Promise 可以通过一个函数来处理,这个处理函数要传递给 then,作为其第二个参数,或者传递给 catch 方法。现在我们没有使用 Promise API 中的方法,应该怎么处理拒绝?可以通过 try 和 catch 来处理。使用 async 函数的时候,拒绝被当作错误来传递,这样它们就可以通过 JavaScript 本身支持的错误处理代码来处理。

function doAsyncOp() { 
 
  return asynchronousOperation() 
 
    .then(function(val) { 
 
      return asynchronousOperation(val); 
 
    }) 
 
    .then(function(val) { 
 
      return asynchronousOperation(val); 
 
    }) 
 
    .catch(function(err) { 
 
      console.error(err); 
 
    }); 
 
}

 

这与我们链式处理的示例非常相似,只是把它的最后一环改成了调用 catch。如果用 async 函数来写,会像下面这样。

async function doAsyncOp () { 
 
  try { 
 
    var val = await asynchronousOperation(); 
 
    val = await asynchronousOperation(val); 
 
    return await asynchronousOperation(val); 
 
  } catch (err) { 
 
    console.err(err); 
 
  } 
 
};

 

它不像其它往 async 函数的转换那样简洁,但是确实跟写同步代码一样。如果你在这里不捕捉错误,它会延着调用链一直向上抛出,直到在某处被捕捉处理。如果它一直未被捕捉,它最终会中止程序并抛出一个运行时错误。Promise 以同样的方式运作,只是拒绝不必当作错误来处理;它们可能只是一个说明错误情况的字符串。如果你不捕捉被创建为错误的拒绝,你会看到一个运行时错误,不过如果你只是使用一个字符串,会失败却不会有输出。

中断 Promise

拒绝原生的 Promise,只需要使用 Promise 构建函数中的 reject 就好,当然也可以直接抛出错误——在 Promise 的构造函数中,在 then 或 catch 的回调中抛出都可以。如果是在其它地方抛出错误,Promise 就管不了了。

这里有一些拒绝 Promise 的示例:

function doAsyncOp() { 
 
  return new Promise(function(resolve, reject) { 
 
    if (somethingIsBad) { 
 
      reject("something is bad"); 
 
    } 
 
    resolve("nothing is bad"); 
 
  }); 
 
} 
 
  
 
/*-- or --*/ 
 
  
 
function doAsyncOp() { 
 
  return new Promise(function(resolve, reject) { 
 
    if (somethingIsBad) { 
 
      reject(new Error("something is bad")); 
 
    } 
 
    resolve("nothing is bad"); 
 
  }); 
 
} 
 
  
 
/*-- or --*/ 
 
  
 
function doAsyncOp() { 
 
  return new Promise(function(resolve, reject) { 
 
    if (somethingIsBad) { 
 
      throw new Error("something is bad"); 
 
    } 
 
    resolve("nothing is bad"); 
 
  }); 
 
}

 

一般来说,最好使用 new Error,因为它会包含错误相关的其它信息,比如抛出位置的行号,以及可能会有用的调用栈。

这里有一些抛出 Promise 不能捕捉的错误的示例:

function doAsyncOp() { 
 
  // the next line will kill execution 
 
  throw new Error("something is bad"); 
 
  return new Promise(function(resolve, reject) { 
 
    if (somethingIsBad) { 
 
      throw new Error("something is bad"); 
 
    } 
 
    resolve("nothing is bad"); 
 
  }); 
 
} 
 
  
 
// assume `doAsyncOp` does not have the killing error 
 
function x() { 
 
  var val = doAsyncOp().then(function() { 
 
    // this one will work just fine 
 
    throw new Error("I just think an error should be here"); 
 
  }); 
 
  // this one will kill execution 
 
  throw new Error("The more errors, the merrier"); 
 
  return val; 
 
}

 

在 async 函数的 Promise 中抛出错误就不会产生有关范围的问题——你可以在 async 函数中随时随地抛出错误,它总会被 Promise 抓住:

async function doAsyncOp() { 
 
  // the next line is fine 
 
  throw new Error("something is bad"); 
 
  if (somethingIsBad) { 
 
    // this one is good too 
 
    throw new Error("something is bad"); 
 
  } 
 
  return "nothing is bad"; 
 
}  
 
  
 
// assume `doAsyncOp` does not have the killing error 
 
async function x() { 
 
  var val = await doAsyncOp(); 
 
  // this one will work just fine 
 
  throw new Error("I just think an error should be here"); 
 
  return val; 
 
}

 

当然,我们永远不会运行到 doAsyncOp 中的第二个错误,也不会运行到 return 语句,因为在那之前抛出的错误已经中止了函数运行。

问题

如果你刚开始使用 async 函数,需要小心嵌套函数的问题。比如,如果你的 async 函数中有另一个函数(通常是回调),你可能认为可以在其中使用 await ,但实际不能。你只能直接在 async 函数中使用 await 。

比如,这段代码无法运行:

async function getAllFiles(fileNames) { 
 
  return Promise.all( 
 
    fileNames.map(function(fileName) { 
 
      var file = await getFileAsync(fileName); 
 
      return parse(file); 
 
    }) 
 
  ); 
 
}

 

第 4 行的 await 无效,因为它是在一个普通函数中使用的。不过可以通过为回调函数添加 async 关键字来解决这个问题。

async function getAllFiles(fileNames) { 
 
  return Promise.all( 
 
    fileNames.map(async function(fileName) { 
 
      var file = await getFileAsync(fileName); 
 
      return parse(file); 
 
    }) 
 
  ); 
 
}

 

你看到它的时候会觉得理所当然,即便如此,仍然需要小心这种情况。

也许你还想知道等价的使用 Promise 的代码:

function getAllFiles(fileNames) { 
 
  return Promise.all( 
 
    fileNames.map(function(fileName) { 
 
      return getFileAsync(fileName).then(function(file) { 
 
        return parse(file); 
 
      }); 
 
    }) 
 
  ); 
 
}

 

接下来的问题是关于把 async 函数看作同步函数。需要记住的是,async 函数内部的的代码是同步运行的,但是它会立即返回一个 Promise,并继续运行外面的代码,比如:

var a = doAsyncOp(); // one of the working ones from earlier 
 
console.log(a); 
 
a.then(function() { 
 
  console.log("`a` finished"); 
 
}); 
 
console.log("hello"); 
 
  
 
/* -- will output -- */ 
 
Promise Object 
 
hello 
 
`a` finished

 

你会看到 async 函数实际使用了内置的 Promise。这让我们思考 async 函数中的同步行为,其它人可以通过普通的 Promise API 调用我们的 async 函数,也可以使用它们自己的 async 函数来调用。

如今,更好的异步代码!

即使你本身不能使用异步代码,你也可以进行编写或使用工具将其编译为 ES5。 异步函数能让代码更易于阅读,更易于维护。 只要我们有 source maps,我们可以随时使用更干净的 ES2017 代码。

有许多可以将异步功能(和其他 ES2015+功能)编译成 ES5 代码的工具。 如果您使用的是 Babel,这只是安装 ES2017 preset 的例子。

Das obige ist der detaillierte Inhalt vonVereinfachen Sie asynchronen Code mit Async-Funktionen (JavaScript-Entwicklungstipps). Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn