├── .gitignore
├── README.md
├── co.js
├── code
├── 1.whatIsCallback.js
├── 2.callbackHell.js
├── 3.callbackToPromise.js
├── 4.whyChoosePromise.js
├── 5.generator.js
├── 6.autoActuator.js
├── 7.asyncFn.js
├── 8.asyncTest.js
├── 9.objectToPromise.js
├── node_modules
│ └── q
│ │ ├── CHANGES.md
│ │ ├── LICENSE
│ │ ├── README.md
│ │ ├── package.json
│ │ ├── q.js
│ │ └── queue.js
├── package-lock.json
└── package.json
├── index.html
├── ppt
├── assets
│ ├── arrow.svg
│ ├── github.svg
│ └── 方正兰亭超细黑简体.ttf
├── component
│ ├── directory
│ │ ├── index.grs
│ │ └── style.css
│ ├── oneParts
│ │ ├── asyncFunction.grs
│ │ ├── asyncTest.grs
│ │ ├── autoActuator.grs
│ │ ├── autoActuatorTest.grs
│ │ ├── callback.grs
│ │ ├── callbackChange.grs
│ │ ├── callbackHell.grs
│ │ ├── callbackToPromise.grs
│ │ ├── generator.grs
│ │ ├── history.grs
│ │ ├── index.grs
│ │ ├── promise.grs
│ │ ├── promiseChangeToDeferred.grs
│ │ ├── summary.grs
│ │ └── whyChoosePromise.grs
│ ├── root
│ │ ├── index.grs
│ │ └── style.css
│ ├── style.css
│ ├── summary
│ │ ├── index.grs
│ │ └── style.css
│ └── twoParts
│ │ ├── arrayToPromise.grs
│ │ ├── coWrap.grs
│ │ ├── index.grs
│ │ ├── objectToPromise.grs
│ │ ├── summary.grs
│ │ ├── theable.grs
│ │ └── toPromise.grs
├── global.css
├── index.js
└── lib
│ ├── css-module.js
│ ├── flexible.js
│ ├── highlight
│ ├── highlight.css
│ └── highlight.pack.js
│ ├── impress.js
│ ├── liberty-plugin.js
│ └── liberty.js
└── 思路.md
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .vscode
3 |
4 | # Editor directories and files
5 | .idea
6 | *.suo
7 | *.ntvs*
8 | *.njsproj
9 | *.sln
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## 这是分享 Js 异步发展历史和介绍 co 源码的 ppt
2 |
3 | 如果对你有帮助,欢迎点个 star,[预览链接](https://imtaotao.github.io/co-share/) 😁
4 |
5 |
6 | ```
7 | 使用静态服务器打开此项目就可以查看 ppt
8 |
9 | +--
10 | +-- ppt (ppt 源码)
11 | +-- code (演示源码,通过 node 运行看效果)
12 | ```
--------------------------------------------------------------------------------
/co.js:
--------------------------------------------------------------------------------
1 | var slice = Array.prototype.slice;
2 | // module.exports = co['default'] = co.co = co;
3 |
4 | // const wrap = co.wrap(function * () {})
5 | // const gen = wrap()
6 |
7 | co.wrap = function (fn) {
8 | createPromise.__generatorFunction__ = fn;
9 | return createPromise;
10 | function createPromise() {
11 | return co.call(this, fn.apply(this, arguments));
12 | }
13 | };
14 |
15 | function co(gen) {
16 | // 保证正确的 this 指向
17 | var ctx = this;
18 | var args = slice.call(arguments, 1);
19 |
20 | // we wrap everything in a promise to avoid promise chaining,
21 | // which leads to memory leak errors.
22 | // see https://github.com/tj/co/issues/180
23 | return new Promise(function(resolve, reject) {
24 | // 调用传入进来的函数,gen 也有可能本身就是一个 generator 对象
25 | if (typeof gen === 'function') gen = gen.apply(ctx, args);
26 | // 如果是个普通对象就返回
27 | if (!gen || typeof gen.next !== 'function') return resolve(gen);
28 |
29 | onFulfilled();
30 |
31 | // 调用 next
32 | function onFulfilled(res) {
33 | var ret;
34 | try {
35 | ret = gen.next(res);
36 | } catch (e) {
37 | return reject(e);
38 | }
39 | next(ret);
40 | return null;
41 | }
42 |
43 | // 出现错误后调用 throw 方法,以便以让 generator 函数中的 try catch 捕获到
44 | function onRejected(err) {
45 | var ret;
46 | try {
47 | ret = gen.throw(err);
48 | } catch (e) {
49 | // 如果连 generator 函数重点 catch 中都报错了就需要传到最外面去了
50 | return reject(e);
51 | }
52 | next(ret);
53 | }
54 |
55 | function next(ret) {
56 | // done 了直接结束整个执行器
57 | if (ret.done) return resolve(ret.value);
58 |
59 | // 转换成 promise
60 | var value = toPromise.call(ctx, ret.value);
61 | if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
62 |
63 | // co 只能支持 function promise generator array object 这几种类型,这和 async 函数有点不一样
64 | return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
65 | + 'but the following object was passed: "' + String(ret.value) + '"'));
66 | }
67 | });
68 | }
69 |
70 | function toPromise(obj) {
71 | // 这条语句会让上面的判断报 TypeError 错误
72 | if (!obj) return obj;
73 |
74 | // 判断是否是一个 promise(包括鸭子类型)
75 | if (isPromise(obj)) return obj;
76 |
77 | // 如果是一个 generator 调用 co, co 会返回一个 promise
78 | if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj);
79 |
80 | if ('function' == typeof obj) return thunkToPromise.call(this, obj);
81 |
82 | // 进行包装
83 | if (Array.isArray(obj)) return arrayToPromise.call(this, obj);
84 |
85 | // 类似 array 的处理
86 | if (isObject(obj)) return objectToPromise.call(this, obj);
87 | return obj;
88 | }
89 |
90 | function thunkToPromise(fn) {
91 | var ctx = this;
92 | return new Promise(function (resolve, reject) {
93 | fn.call(ctx, function (err, res) {
94 | if (err) return reject(err);
95 | if (arguments.length > 2) res = slice.call(arguments, 1);
96 | resolve(res);
97 | });
98 | });
99 | }
100 |
101 | function arrayToPromise(obj) {
102 | // 对每个元素包装成 promise
103 | return Promise.all(obj.map(toPromise, this));
104 | }
105 |
106 | // 对 对象的每个元素进行包装
107 | function objectToPromise(obj){
108 | var results = new obj.constructor();
109 | var keys = Object.keys(obj);
110 | var promises = [];
111 |
112 | for (var i = 0; i < keys.length; i++) {
113 | var key = keys[i];
114 | var promise = toPromise.call(this, obj[key]);
115 |
116 | // 如果是 promise,就放到 promise 数组中去,等待 promise.all 调用
117 | if (promise && isPromise(promise)) defer(promise, key);
118 | else results[key] = obj[key];
119 | }
120 | // 对所有包装的 item 调用 promise.all
121 | return Promise.all(promises).then(function () {
122 | return results;
123 | });
124 |
125 | function defer(promise, key) {
126 | // 预定义结果中的键,js 引擎喜欢稳定的对象
127 | results[key] = undefined;
128 | // 往数组种添加 promise
129 | // [1, 2, 3, promise, promise]
130 | promises.push(promise.then(function (res) {
131 | results[key] = res;
132 | }));
133 | }
134 | }
135 |
136 | // promise theable 所以只需要判读是否有 then 函数(鸭子类型)
137 | function isPromise(obj) {
138 | return 'function' == typeof obj.then;
139 | }
140 |
141 | // 只要你部署了遍历器接口,需要 throw 是因为要 try/catch 捕捉到
142 | function isGenerator(obj) {
143 | return 'function' == typeof obj.next && 'function' == typeof obj.throw;
144 | }
145 |
146 |
147 | function isGeneratorFunction(obj) {
148 | var constructor = obj.constructor;
149 | if (!constructor) return false;
150 | if ('GeneratorFunction' === constructor.name || 'GeneratorFunction' === constructor.displayName) return true;
151 | return isGenerator(constructor.prototype);
152 | }
153 |
154 | // 只处理 {}
155 | function isObject(val) {
156 | return Object == val.constructor;
157 | }
--------------------------------------------------------------------------------
/code/1.whatIsCallback.js:
--------------------------------------------------------------------------------
1 | // 以下 demo 演示为什么需要回调
2 | function ordinary () {
3 | let i = 1
4 | // ...
5 | return i
6 | }
7 |
8 | function callback () {
9 | let i = 1
10 | setTimeout(() => {
11 | return i
12 | })
13 | }
14 |
15 | // demo
16 | const result = ordinary()
17 | console.log(result) // 1
18 |
19 | const resultTwo = callback()
20 | console.log(resultTwo) // undefined
21 |
22 | // 人们尝试理解异步函数的时候,最大的问题是程序执行的顺序,这取决异步行为,而这是一个无法预测的行为
23 | // 也被函数式编程称呼为副作用,因为不可预测的行为,所以是大部分 bug 的来源。是需要花费很大力气解决的事情
24 |
25 | // 疑问:回调函数带来什么样的问题
--------------------------------------------------------------------------------
/code/2.callbackHell.js:
--------------------------------------------------------------------------------
1 | getData(function (a) {
2 | getMoreData(a, function (b) {
3 | getMoreData(b, function (c) {
4 | getMoreData(c, function (d) {
5 | // ...
6 | })
7 | })
8 | })
9 | })
10 |
11 | // 需要合理的封装和简化,这需要开发人员自身的水平和认知决定
12 | function fn (a, cb) {
13 | getMoreData(a, function (b) {
14 | getMoreData(b, function (c) {
15 | getMoreData(c, function (d) {
16 | cb(d)
17 | })
18 | })
19 | })
20 | }
21 |
22 | // 通过层层封装,抽象出模块和通用的类来保证代码是浅层
23 | getData(function (a) {
24 | fn(a, function (d) {
25 | // ...
26 | })
27 | })
28 |
29 | // 疑问:有什么办法不依靠开发人员的水平来维护 callback hell
--------------------------------------------------------------------------------
/code/3.callbackToPromise.js:
--------------------------------------------------------------------------------
1 | getData(function (a) {
2 | getMoreData(a, function (b) {
3 | getMoreData(b, function (c) {
4 | getMoreData(c, function (d) {
5 | // ...
6 | })
7 | })
8 | })
9 | })
10 |
11 | // 如果是 promise,这将 callback 变成了一种扁平化的结构。相对于 callback 更加友好
12 | getData()
13 | .then(getMoreData)
14 | .then(getMoreData)
15 | .then(getMoreData)
16 | .then(function (d) {
17 | // ...
18 | })
19 |
20 | // 疑问:promise 和 其他方案的竞争,为什么是 promise 胜出了
--------------------------------------------------------------------------------
/code/4.whyChoosePromise.js:
--------------------------------------------------------------------------------
1 | // 社区不止有 promise 一个概念
2 | // 在 jQuery1.5 中就有了 deferred 的概念
3 | // 为什么最后选择了 promise
4 | // https://groups.google.com/forum/#!topic/bluebird-js/mUiX2-vXW2s
5 | // es6 提出了 new Promise() 这种构建 promise 的方式,以下是俩 demo
6 | // 1. promise 和 deferred 使用方式不同
7 | // 2. deferred 的局限在哪里
8 |
9 | const create = () => require('q').defer()
10 |
11 | // 1. deferred 和 promise 效果是一样的
12 | function demoOne () {
13 | // deferred promise 可以理解为订阅发布模式实现
14 | // 我们可以在 A 模块注册,在 B 模块触发
15 | const deferred = create()
16 | deferred.promise.then(v => console.log(v))
17 | setTimeout(() => deferred.resolve('tao'), 500)
18 |
19 | // promise 在构建时就立即进入到触发阶段,不够自由,但是可以把 resolve 拿出来
20 | // 你即使可以在其他模块引用 p 模块,但是此时的 promise 状态可能已经更改了
21 | new Promise(resolve => {
22 | setTimeout(() => resolve('tao'), 500)
23 | })
24 | .then(v => console.log(v))
25 | }
26 |
27 | // 2. 为什么最终还是选择了 promise
28 | function demoTwo () {
29 | const deferred = create()
30 | deferred.promise.catch(reason => console.log(reason))// 不触发
31 | setTimeout(() => {
32 | // throw 'err'
33 | // 必须用 try catch 然后通过 deferrd.reject 触发
34 | try {
35 | throw 'errDeferred'
36 | } catch (err) {
37 | deferred.reject(err)
38 | }
39 | })
40 |
41 | // promise 由于是自执行,自动捕捉异常
42 | new Promise(() => { throw 'errPromise' })
43 | .catch(reason => console.log(reason))
44 | }
45 |
46 | // demoOne()
47 | demoTwo()
48 |
49 | // 通过 promise 创建一个 deferred,可以替换上面的 q 创建的
50 | function createDeferred () {
51 | let resolve, reject
52 |
53 | const promise = new Promise((_resolve, _reject) => {
54 | resolve = _resolve
55 | reject = _reject
56 | })
57 | return { promise, resolve, reject }
58 | }
59 |
60 | // 结论
61 | // promise 首先应该是一个异步流程控制的解决方案,流程控制包括了正常的数据流和异常流程处理。(捕捉异常,线程安全)
62 | // 而 deferred 的方式存在一个致命的缺陷
63 | // 就是 promise 链的第一个promise(deferred.promise)的触发阶段抛出的异常是不交由 promise 自动处理的
64 |
65 | // 疑问:但是 promise 的写法看起来还是不够同步,有什么办法呢?幸好我们有了 generator 函数
--------------------------------------------------------------------------------
/code/5.generator.js:
--------------------------------------------------------------------------------
1 | // 代码演变历程
2 | // callback -> promise -> generator
3 | // 不同演变进程下的代码写法,我们可以看到是以越来越同步的写法进行
4 |
5 | const getData = cb => {
6 | return new Promise(resolve => {
7 | cb && cb(1)
8 | resolve(1)
9 | })
10 | }
11 | const getMoreData = (v, cb) => {
12 | cb && cb(v + 1)
13 | return v + 1
14 | }
15 |
16 | // 1. callback
17 | getData(function (a) {
18 | getMoreData(a, function (b) {
19 | getMoreData(b, function (c) {
20 | getMoreData(c, function (d) {
21 | console.log('callback', d)
22 | })
23 | })
24 | })
25 | })
26 |
27 | // 2. promise
28 | getData()
29 | .then(getMoreData)
30 | .then(getMoreData)
31 | .then(getMoreData)
32 | .then(function (d) {
33 | console.log('promise', d)
34 | })
35 |
36 | // 3. generator
37 | const gen = (function * () {
38 | const a = yield 1
39 | const b = yield a + 1
40 | const c = yield b + 1
41 | const d = yield c + 1
42 | return d
43 | })()
44 |
45 | const a = gen.next()
46 | const b = gen.next(a.value)
47 | const c = gen.next(b.value)
48 | const d = gen.next(c.value)
49 | console.log('generator', d.value)
50 |
51 | // 可以看到使用 generato 函数已经做到语法上的同步了,但是 generator 函数需要手动调用 next
52 | // 疑问:我们可不可以把手动调用的过程封装为一个函数,让他自动执行
--------------------------------------------------------------------------------
/code/6.autoActuator.js:
--------------------------------------------------------------------------------
1 | // 一个 generator 自执行器
2 | // 我们把这个自执行函数封装为 co
3 |
4 | function co (fn, ...args) {
5 | return new Promise((resolve, reject) => {
6 | // 处理 generator 函数
7 | const gen = fn(...args)
8 |
9 | // 自动执行下一步
10 | const next = result => {
11 | let value = result.value
12 | if (result.done) return resolve(value)
13 |
14 | // 如果是 generator 函数
15 | if (value && value.constructor && value.constructor.name === 'GeneratorFunction') {
16 | value = co(value)
17 | }
18 |
19 | // 就算 value 是 promise,也包裹起来
20 | Promise.resolve(value).then(onFulfilled, onRejected)
21 | }
22 |
23 | const onFulfilled = res => {
24 | let result
25 | try {
26 | result = gen.next(res)
27 | next(result)
28 | } catch (err) {
29 | return reject(err)
30 | }
31 | }
32 |
33 | const onRejected = err => {
34 | let result
35 | try {
36 | result = gen.throw(err)
37 | next(result)
38 | } catch (e) {
39 | return reject(e)
40 | }
41 | }
42 |
43 | onFulfilled()
44 | })
45 | }
46 |
47 | // ---- test ----
48 | const ret = co(function * () {
49 | const a = yield 1
50 | const b = yield a + 1
51 | const c = yield b + 1
52 | const d = yield c + 1
53 | return d
54 | })
55 | ret.then(v => console.log(v))
56 |
57 | // demo
58 | function demo () {
59 | const fn = v => {
60 | return new Promise(resolve => {
61 | setTimeout(() => resolve(v), 200)
62 | })
63 | }
64 |
65 | const ret = co(function * () {
66 | const a = yield fn(1)
67 | const b = yield fn(a + 1)
68 | const c = yield fn(b + 1)
69 | const d = yield fn(c + 1)
70 | return d
71 | })
72 | ret.then(v => console.log(v))
73 | }
74 |
75 | function demoTwo () {
76 | const ret = co(function * () {
77 | try {
78 | throw 'err'
79 | } catch (err) {
80 | console.log(err)
81 | throw 'err'
82 | }
83 | })
84 |
85 | ret.catch(err => console.log(err))
86 | }
87 |
88 | demo()
89 | demoTwo()
90 |
91 | // 疑问:generator 函数和 promise 能不能封装一下,做成一个新的语法糖(肯定是可以的)
--------------------------------------------------------------------------------
/code/7.asyncFn.js:
--------------------------------------------------------------------------------
1 | const ret = (async function () {
2 | const a = await 1
3 | const b = await a + 1
4 | const c = await b + 1
5 | const d = await c + 1
6 | return d
7 | })()
8 | ret.then(v => console.log(v))
9 |
10 | // demo
11 | function demo () {
12 | const fn = v => {
13 | return new Promise(resolve => {
14 | setTimeout(() => resolve(v), 200)
15 | })
16 | }
17 |
18 | const ret = (async function () {
19 | const a = await fn(1)
20 | const b = await fn(a + 1)
21 | const c = await fn(b + 1)
22 | const d = await fn(c + 1)
23 | return d
24 | })()
25 | ret.then(v => console.log(v))
26 | }
27 |
28 | function demoTwo () {
29 | const ret = (async function () {
30 | try {
31 | throw 'err'
32 | } catch (err) {
33 | console.log(err)
34 | throw 'err'
35 | }
36 | })()
37 |
38 | ret.catch(err => console.log(err))
39 | }
40 |
41 | demo()
42 | demoTwo()
43 |
44 | // 疑问:原生的 async 函数是否真的用到了 promise(真的用到了,看下面的 demo)
--------------------------------------------------------------------------------
/code/8.asyncTest.js:
--------------------------------------------------------------------------------
1 | // 测试原生的 async 函数是否使用了 promise
2 | // 鸭子模型,不要判断是不是鸭子,只要判断像不像鸭子,这里就是判断像不像 promise
3 | const p = {
4 | then (resolve, reject) {
5 | console.log('into')
6 | setTimeout(() => resolve('tao'), 1000)
7 |
8 | // reject 会被 async 的 try catch 捕获
9 | // reject('err')
10 | }
11 | };
12 |
13 | (async function () {
14 | try {
15 | const v = await p
16 | console.log(v)
17 | } catch (err) {
18 | console.log(err)
19 | }
20 | })()
21 |
22 |
23 | // 看看 co 是在整个异步问题的解决历史上的哪个关键节点上。我们要看 co 源码了
--------------------------------------------------------------------------------
/code/9.objectToPromise.js:
--------------------------------------------------------------------------------
1 | function isPromise(obj) {
2 | return 'function' == typeof obj.then;
3 | }
4 | function isGenerator(obj) {
5 | return 'function' == typeof obj.next && 'function' == typeof obj.throw;
6 | }
7 | function isGeneratorFunction(obj) {
8 | var constructor = obj.constructor;
9 | if (!constructor) return false;
10 | if ('GeneratorFunction' === constructor.name || 'GeneratorFunction' === constructor.displayName) return true;
11 | return isGenerator(constructor.prototype);
12 | }
13 | function isObject(val) {
14 | return Object == val.constructor;
15 | }
16 | function toPromise(obj) {
17 | if (!obj) return obj;
18 | if (isPromise(obj)) return obj;
19 | if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj);
20 | if ('function' == typeof obj) return thunkToPromise.call(this, obj);
21 | if (Array.isArray(obj)) return arrayToPromise.call(this, obj);
22 | if (isObject(obj)) return objectToPromise.call(this, obj);
23 | return obj;
24 | }
25 |
26 | function arrayToPromise(obj) {
27 | return Promise.all(obj.map(toPromise, this));
28 | }
29 |
30 | function objectToPromise(obj){
31 | var results = new obj.constructor();
32 | var keys = Object.keys(obj);
33 | var promises = [];
34 |
35 | for (var i = 0; i < keys.length; i++) {
36 | var key = keys[i];
37 | var promise = toPromise.call(this, obj[key]);
38 |
39 | // 如果是 promise
40 | if (promise && isPromise(promise)) defer(promise, key);
41 | else results[key] = obj[key];
42 | }
43 | // 对所有包装的 item 调用 promise.all
44 | return Promise.all(promises).then(function () {
45 | return results;
46 | });
47 |
48 | function defer(promise, key) {
49 | // 预定义结果中的键,js 引擎喜欢稳定的对象
50 | results[key] = undefined;
51 | // 往数组种添加 promise
52 | // [1, 2, 3, promise, promise]
53 | promises.push(promise.then(function (res) {
54 | results[key] = res;
55 | }));
56 | }
57 | }
58 |
59 | objectToPromise({
60 | a: 1,
61 | b: [2, 3],
62 | c: new Promise(resolve => {
63 | setTimeout(() => resolve(1), 500)
64 | })
65 | }).then(res => {
66 | console.log(res);
67 | })
--------------------------------------------------------------------------------
/code/node_modules/q/CHANGES.md:
--------------------------------------------------------------------------------
1 |
2 | ## 1.5.1
3 |
4 | - Q.any now annotates its error message to clarify that Q.any was involved and
5 | includes only the last error emitted. (Ivan Etchart)
6 | - Avoid domain.dispose during tests in preparation for Node.js 9. (Anna
7 | Henningsen)
8 |
9 | ## 1.5.0
10 |
11 | - Q.any gives an error message from the last rejected promise
12 | - Throw if callback supplied to "finally" is invalid (@grahamrhay)
13 | - Long stack trace improvements, can now construct long stack traces
14 | across rethrows.
15 |
16 | ## 1.4.1
17 |
18 | - Address an issue that prevented Q from being used as a `
35 |
36 |
37 |
38 |
39 |
75 |