[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