[TOC]
# Callbacks
傳統解決異步的方法:回調函數
~~~
// levelOne() is called a high-order function because
// it accepts another function as its parameter.
function levelOne(value, callback) {
var newScore = value + 5;
callback(newScore);
}
// Please note that it is not mandatory to reference the callback function (line #3) as callback, it is named so just for better understanding.
function startGame() {
var currentScore = 5;
console.log('Game Started! Current score is ' + currentScore);
// Here the second parameter we're passing to levelOne is the
// callback function, i.e., a function that gets passed as a parameter.
levelOne(currentScore, function (levelOneReturnedValue) {
console.log('Level One reached! New score is ' + levelOneReturnedValue);
});
}
startGame();
~~~
現在想象一下,如果我們必須為另外10個 levels 實現相同的邏輯,那么這個代碼將成為什么。你已經恐慌了嗎?好吧,我!隨著嵌套回調函數的數量增加,讀取代碼變得更加困難,甚至更難調試。
這通常被親切地稱為 callback hell **回調地獄**。有沒有辦法解決這個回調地獄?
# Promises
~~~
// This is how a sample promise declaration looks like. The promise constructor
// takes one argument which is a callback with two parameters, `resolve` and
// `reject`. Do something within the callback, then call resolve if everything
// worked, otherwise call reject.
var promise = new Promise(function(resolve, reject) {
// do a thing or twenty
if (/* everything turned out fine */) {
resolve("Stuff worked!");
}
else {
reject(Error("It broke"));
}
});
~~~
使得整個代碼更易于閱讀,更容易理解發生了什么,以及接下來發生了什么等等。
它們更容易組合,這大致意味著組合多個 Promise “就行”,而組合多個回調常常不行。
# Generators/Yield
由于 Generator 函數可以交出函數的執行權,整個 Generator 函數可以看作一個封裝的異步任務,或者說是異步任務的容器。
1)遇到`yield`表達式,則暫停執行后面的操作,并緊跟在`yield`后面的那個表達式的值,作為返回的對象的`value`屬性值。
2)下次調用`next`方法時,再繼續往下執行,直到遇到下一個`yield`表達式。
3)若沒有再遇到新的`yield`表達式,則一直運行到函數結束,直到`return`語句為止,并將`return`語句后面的表達式的值,作為返回的對象的`value`屬性值。
4)若該函數沒有`return`語句,則返回的對象的`value`屬性值為`undefined`。
> Generator 函數可以返回一系列的值,因為可以有任意多個`yield`;正常函數只能返回一個值,因為只能執行一次`return`
異步操作需要暫停的地方,都用yield語句注明。Generator 函數的執行方法如下:
~~~
function* gen(x) {
var y = yield x + 2;
return y;
}
var g = gen(1);
g.next() // { value: 3, done: false }
g.next() // { value: undefined, done: true }
~~~
調用函數,返回一個內部指針(遍歷器)g。
# Async/Await
ECMA2017以來,javascript支持 Async- await,在NodeJs的7.6版本中實現。它們允許您編寫基于承諾的代碼,就像它是同步代碼一樣,但不會阻塞主線程。它們使您的異步代碼不那么“聰明”,更具可讀性。
說實話,async-awaits只不過是 `Promise` 之上的語法糖(即最終會被解析為 Promise 形式),但它使異步代碼的外觀和行為更像是同步代碼,這正是它的力量所在。
`await`會暫停`async`后面的代碼,先執行`async`外面的同步代碼(**等待承諾時,函數將以非阻塞方式暫停,直到承諾處理完成**),等著 Promise 對象`fulfilled`,然后把`resolve`的參數作為`await`表達式的運算結果。
`await` 有效地使每個調用看起來好像是同步的,而不是阻止JavaScript的單線程。此外, **`async` 函數總是返回一個 `Promise`** ,因此它們可以被其他 `async` 函數調用。
~~~
function timeout(ms){
return new Promise((resolve) =>{
console.log("Enter Promise");
setTimeout(function(){
console.log("==setTimeout==");
resolve('resolve!');
}, ms)
})
}
async function asyncPrint(value, ms) {
await timeout(ms);
console.log(value);
/* body... */
}
console.log('!!!!START!!!')
asyncPrint('hello world', 550).then(res=>{ //如果這里的換成 alert('pending'); 你可以看到被阻塞的效果!!
console.log('Done');
})
console.log('!!!!END!!!');
~~~
只有返回 `promise` 或者 具有`async` 關鍵字修飾的 函數 才是 “awaitable”。
~~~
function levelOne(value) {
var promise, newScore = value + 5;
return promise = new Promise(function(resolve) {
resolve(newScore);
});
}
function levelTwo(value) {
var promise, newScore = value + 10;
return promise = new Promise(function(resolve) {
resolve(newScore);
});
}
function levelThree(value) {
var promise, newScore = value + 30;
return promise = new Promise(function(resolve) {
resolve(newScore);
});
}
// the async keyword tells the javascript engine the any function inside this function having the keyword await, should be treated as asynchronous code and should continue executing only once that function resolves or fails.
async function startGame() {
var currentScore = 5;
console.log('Game Started! Current score is ' + currentScore);
currentScore = await levelOne(currentScore);
console.log('You have reached Level One! New score is ' + currentScore);
currentScore = await levelTwo(currentScore);
console.log('You have reached Level Two! New score is ' + currentScore);
currentScore = await levelThree(currentScore);
console.log('You have reached Level Three! New score is ' + currentScore);
}
startGame();
~~~
注意:由于同步特性,`Async / await` 稍微慢一些。連續多次使用它時應該小心,因為await關鍵字會停止執行后面的所有代碼 - 就像在同步代碼中一樣。
~~~
// this function will return true after 1 second (see the async keyword in front of function)
async function returnTrue() {
// create a new promise inside of the async function
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve(true), 1000) // resolve
});
// wait for the promise to resolve
let result = await promise;
// console log the result (true)
console.log(result);
}
// call the function
returnTrue();
~~~
## 使用try / catch 錯誤處理
因為 `await` 等待的 Promise 可能出現異常錯誤,所以捕獲錯誤,可以放在`try..catch..`中,或者 `then()..catch()..`。
~~~
async function getSomeData(value){
try {
const result = await fetchTheData(value);
return result;
}
catch(error){ //任何錯誤都將在該catch塊中結束
// Handle error
}
}
~~~
`Async / Await` 是我們代碼庫的最佳選擇:
1. `Async / Await` 允許使用更少的代碼行,更少的輸入和更少的錯誤,提供簡潔明了的代碼庫。最終,它使復雜的嵌套代碼再次可讀。
2. 使用 `try / catch` 處理錯誤(在一個地方,而不是在每個調用中)
3. 錯誤堆棧是有意義的,而不是從Promises收到的模糊錯誤,它們很大并且很難找到錯誤發生的位置。最重要的是,錯誤指向錯誤發生的函數。
[Generators/Yield 與 Async/Await 關系](http://www.ruanyifeng.com/blog/2015/05/async.html)
# Observables
https://medium.com/front-end-hacking/modern-javascript-and-asynchronous-programming-generators-yield-vs-async-await-550275cbe433
# Coroutine(協程)
傳統的編程語言,早有異步編程的解決方案(其實是多任務的解決方案)。其中有一種叫做"協程"(coroutine),意思是多個線程互相協作,完成異步任務。協程并不是一個新的概念,其他語言中很早就有了。
它的運行流程大致如下:
* 第一步,協程A開始執行
* 第一步,協程A執行到一半,進入暫停,執行權轉移到協程B。
* 第三步,(一段時間后)協程A恢復執行
* 上面流程的協程A,就是異步任務,因為它分成兩段(或多段)執行。
協程既可以用單線程實現,也可以用多線程實現。
多個線程(單線程的情況下,即多個函數)可以并行執行,但是只有一個線程(或函數)處于正在運行的狀態,其他線程(或函數)都處于暫停態,線程(或函數)之間可以交換執行權,也就是說,一個線程(或函數)執行到一半,可以暫停執行,將執行權交給另一個線程(或函數),等到稍后收回執行權的時候,再恢復執行。這種可以并行執行、交換執行權的線程(或函數),就稱為協程。
**所謂協程:是指多個線程相互協作,完成異步任務。**
Generator 函數是協程在 ES6 中的實現,最大特點就是可以交出函數的執行權。
# ---來一題---
出個題考考大家吧:
~~~
async function as1(){
console.log('as1 start');
await as2(); // 會阻塞后面代碼,跳出當前 async 函數繼續執行外部代碼
console.log('as1 end');
}
// 不是 promise resolve,會導致當前微任務的外部任務完成,再回頭執行該函數
async function as2(){
console.log('as2');
}
console.log('script start');
setTimeout(function(){
console.log('setTimeout');
},0)
as1();
new Promise(function(resolve){
console.log('prom1');
resolve();
}).then(function(){
console.log('prom2');
});
console.log('script end');
~~~
# 參考
> [Generator 函數的含義與用法](http://www.ruanyifeng.com/blog/2015/04/generator.html)
> [ES6 異步進階第二步:Generator 函數](https://www.jianshu.com/p/8c85189e7605 )
> [JavaScript中的Generator(生成器)](https://cloud.tencent.com/developer/article/1601616)
> [Generator函數](https://cloud.tencent.com/developer/article/1663312)
[Handling Concurrency with Async/Await in JavaScript](https://blog.vanila.io/handling-concurrency-with-async-await-in-javascript-8ec2e185f9b4)
[深入理解 JavaScript 異步](https://github.com/wangfupeng1988/js-async-tutorial)
- 步入JavaScript的世界
- 二進制運算
- JavaScript 的版本是怎么回事?
- JavaScript和DOM的產生與發展
- DOM事件處理
- js的并行加載與順序執行
- 正則表達式
- 當遇上this時
- Javascript中apply、call、bind
- JavaScript的編譯過程與運行機制
- 執行上下文(Execution Context)
- javascript 作用域
- 分組中的函數表達式
- JS之constructor屬性
- Javascript 按位取反運算符 (~)
- EvenLoop 事件循環
- 異步編程
- JavaScript的九個思維導圖
- JavaScript奇淫技巧
- JavaScript:shim和polyfill
- ===值得關注的庫===
- ==文章==
- JavaScript框架
- Angular 1.x
- 啟動引導過程
- $scope作用域
- $q與promise
- ngRoute 和 ui-router
- 雙向數據綁定
- 規范和性能優化
- 自定義指令
- Angular 事件
- lodash
- Test