概念
首先需要强调一下:同步不等于阻塞,异步也不等同于非阻塞
其实同步/异步是一种消息通知机制
阻塞和非阻塞的主体是:程序
而同步和异步的主体是:消息
Q:为啥需要这么麻烦的澄清这个关系呢?
A:因为也有同步不阻塞、还有异步阻塞,但是在 JavaScript 中,我们只讨论 同步阻塞
和 异步非阻塞
比较绕,没关系,先知道有这么个东西就可以。
栗子 1:方块的运动
我们来试着做一些东西吧,效果如下图
html + css
样式和 dom 元素如下
1 2 3 4 5 6 7 8 9 10 11 12 13
| <style> .box { width: 100px; height: 100px; background: red; position: absolute; left: 0px; top: 0px; } </style> <body> <div class="box"></div> </body>
|
分析
其实就是让方块儿右下左上,顺时针转了一圈
但是!这里是有顺序的,先右移完成后,然后下移,接着左移,最后上移,这应该是同步的对吧!
所以写程序也要是同步的写法。
回调函数 + 回调地狱
比较原始的方法就是用回调函数来写同步的程序了。
我们先写一个移动功能的函数,然后在调用它四遍即可~
回调函数:就是被当作参数传递的函数。百度百科:回调函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| function move(ele, arg, target, cb) { let start = parseInt(window.getComputedStyle(ele, null)[arg]); let direction = (target - start) / Math.abs(target - start); let speed = direction * 5; function fn() { let now = parseInt(window.getComputedStyle(ele, null)[arg]); if (now === target) { cb && cb("结束"); } else { ele.style[arg] = now + speed + "px"; window.requestAnimationFrame(fn); } } fn(); }
let ele = document.querySelector(".box");
move(ele, "left", 200, function (res) { move(ele, "top", 200, function (res) { move(ele, "left", 0, function (res) { move(ele, "top", 0, function (res) { console.log(res); }); }); }); });
|
通过上面的代码可以看到,回调解决同步的问题会产生层层嵌套的代码,这个问题叫做回调地狱。
回调地狱的代码因为是层层嵌套的,如果这个功能很复杂的话,嵌套的层数可能会非常的多,导致后期维护起来十分困难。所以,Promise
应运而生。
额外知识:
window.requestAnimationFrame(fn)
:调用 gpu,节约内存;而且动画比定时器的要流畅。
- 用
setTimeout
也可以,不过会感觉到卡顿
Promise 期约
首先呢,我不先介绍它是什么,而是看他如何解决回调地狱,试着将上面的栗子写成 Promise。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| function move(ele, arg, target) { return new Promise((resolve, reject) => { let start = parseInt(window.getComputedStyle(ele, null)[arg]); let dis = (target - start) / Math.abs(target - start); let speed = dis * 5; function fn() { let now = parseInt(window.getComputedStyle(ele, null)[arg]); if (now === target) { resolve("运动完成"); } else { ele.style[arg] = now + speed + "px"; window.requestAnimationFrame(fn); } } fn(); }); }
let ele = document.querySelector(".box");
move(ele, "left", 200) .then((res) => { return move(ele, "top", 200); }) .then((res) => { return move(ele, "left", 0); }) .then((res) => { return move(ele, "top", 0); }) .then((res) => { console.log(res); }) .catch((err) => { console.log(err); });
|
好的,对 promise
有了使用的直观感觉了!我们接下来就看一看相关基础知识吧。
基础
我们先 new
一个 promise
对象,然后打印它
1 2
| let promise = new Promise((resolve, reject) => {}); console.log("promise:>>", promise);
|
可以在控制台看到以下信息:
__proto__
:promise
对象的原型链
[[PromiseState]]: "pending"
:期约的状态
[[PromiseResult]]: undefined
:成功或失败时,所携带的值(默认为 undefined
)
而且有一点要知道,new Promise
之后就会立即执行里面的语句
1 2 3 4
| new Promise((resolve, reject) => { console.log(1); });
|
PromiseState
PromiseState
有三种状态:
pending
:执行中
fulfilled
:成功
rejected
:失败
当我们在回调中调用了 resolve()
的时候 ,这个对象就会从 pending
转换成 fulfilled
1 2 3 4
| let p = new Promise((resolve, reject) => { resolve(); }); setTimeout(console.log, 0, p);
|
反之,如果调用了 reject()
,程序就会抛出错误,并且状态变成 rejected
1 2 3 4
| let p = new Promise((resolve, reject) => { reject(); }); setTimeout(console.log, 0, p);
|
then 的返回值
p.then()
之后,不管你写没写回调函数,默认都会返回一个 promise
对象,所以它才能链式调用。
then 的成功回调返回有三种情况
第一种:返回空,此时默认会返回一个 fulfilled
的 promise
对象
1 2 3 4 5 6 7
| let p = new Promise((resolve, reject) => { resolve(); }); let p2 = p.then(() => { return; }); console.log(p2);
|
第二种:返回 非 promise 对象,此时,会将数据放到 PromiseResult
中
1 2 3 4 5 6 7 8
| let p = new Promise((resolve, reject) => { resolve(1); }); let p2 = p.then((res) => { return 1111; }); console.log(p2);
|
第三种:返回 promise
对象,此时 p2
的状态就是 return promise()
中的对象状态了
1 2 3 4 5 6 7 8 9
| let p = new Promise((resolve, reject) => { resolve(1); }); let p2 = p.then((res) => { return new Promise((resolve, reject) => { reject("我是p2"); }); }); console.log(p2);
|
我自己实验出来的一个理解
首先我不写 p2
的 onRejected
回调函数。结果是报错的,如下图所示
1 2 3 4 5 6 7
| let p = new Promise((resolve, reject) => { reject("我是p1"); }); let p2 = p.then((res) => { return res; }); console.log(p2);
|
之后我加上 onrejected
回调函数
1 2 3 4 5 6 7 8 9 10 11 12 13
| let p = new Promise((resolve, reject) => { reject("我是p1"); }); let p2 = p.then( (res) => { return res; }, (err) => { return err; } ); console.log(p2);
|
我原先以为 p1
的状态决定了 p2
的状态。
总结以下:也就是说啊,p2
的状态跟 p1
并不是那么亲密。
只要我 p2
处理完了,那我的状态就可以变成 fulfilled
了,跟你 p1
成功失败没啥关系了,但是,前提得是 p1
不为pending
。
错误笔记
给大家看看我原先写的 promise()
,现在回看一下,感觉好傻啊,因为原先知道,写 promise()
就是为了避免回调地狱,然而下面的方法跟回调地狱没啥区别(⊙﹏⊙)
1 2 3 4
| move(ele, "left", 200).then((res) => { move(ele, "top", 200).then((res) => {}); });
|
async 和 await
在 es2017 中添加的新的语法糖
首先需要声明一个 async
函数,然后再里面声明 await
的函数
注:await 后面一定要跟一个 promise 对象
可以看一下移动的方块儿的改法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| function move(ele, arg, target) { return new Promise((resolve, reject) => { let start = parseInt(window.getComputedStyle(ele, null)[arg]); let dis = (target - start) / Math.abs(target - start); let speed = dis * 5; function fn() { let now = parseInt(window.getComputedStyle(ele, null)[arg]); if (now === target) { resolve("运动完成"); } else { ele.style[arg] = now + speed + "px"; window.requestAnimationFrame(fn); } } fn(); }); } let ele = document.querySelector(".box"); async function fn() { try { await move(ele, "left", 200); await move(ele, "top", 200); await move(ele, "left", 0); let res = await move(ele, "top", 0); } catch (err) { console.log(err); } } fn();
|
这是另一个栗子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| async function myfn() { try { await new Promise((resolve, reject) => { setTimeout(() => { console.log(1111); resolve(); }, 1000); }); await new Promise((resolve, reject) => { setTimeout(() => { console.log(2222); resolve(); }, 2000); }); await new Promise((resolve, reject) => { setTimeout(() => { console.log(3333); resolve(); }, 3000); }); } catch (err) { console.log(err); } } myfn();
|
async/await 的原理:Generator
如果要介绍 generator
的话,需要先了解一下什么是迭代器iteratioin
迭代器
所谓的迭代器,就是反复的执行某一段代码,通常会有明确的终止条件。
我们以 for of
举例:for of
能遍历数组,但是不能遍历对象,是因为对象它不是可迭代的(obj is not iterable
)
在 obj
的原型上没有obj[Symbol.iterator]
这个函数,而for of
帮我们做的就是执行这个函数。
1 2 3 4 5 6 7 8 9 10 11 12 13
| let arr = [1, 2, 3]; for (let item of arr) { console.log(item); }
let obj = { key1: "value1", key2: "value2", key3: "value3", }; for (let item of obj) { console.log(item); }
|
那么想要用 for of
遍历对象,我们怎么办呢?我们可以给 obj
写一个 迭代器
首先,for of
会调用对象中的 obj[Symbol.iterator]
这个函数
这个函数返回一个对象
对象中又有一个next
方法
next 方法执行后返回{ done: false, value: values[index++] }
,其中 done 代表是否迭代结束,value 表示当前迭代的值
哎嘿,闭包的用法吧
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| let obj = { key1: "value1", key2: "value2", key3: "value3", }; obj[Symbol.iterator] = function () { let values = Object.values(obj); let index = 0; return { next() { if (index >= values.length) { return { done: true, }; } else { return { done: false, value: values[index++], }; } }, }; }; for (let val of obj) { console.log(val); }
|
我们可以试着这么执行一下
1 2 3 4 5
| let values = obj[Symbol.iterator](); console.log(values.next()); console.log(values.next()); console.log(values.next()); console.log(values.next());
|
这样一步一步的执行,是不是有点像async/await
了?
Generator
Generator
函数的声明方式是 在函数名和 function 中间加个 *
号
1
| function* generatorFn() {}
|
函数内可以声明 yield
语句
1 2 3 4 5
| function* generatorFn() { yield 1; yield 2; yield 3; }
|
当我们执行这个函数后,返回一个可迭代的 generator
对象
1 2 3 4 5 6 7
| function* generatorFn() { yield 1; yield 2; yield 3; } let f = generatorFn(); console.log(f);
|
之后执行它的 next
方法
1 2 3 4 5 6 7 8 9 10 11
| function* generatorFn() { yield 1; yield 2; yield 3; } let f = generatorFn(); console.log(f); console.log(f.next()); console.log(f.next()); console.log(f.next()); console.log(f.next());
|
看,是不是和上面的迭代器联系起来了!
那我们如何通过它来实现 async/await
呢
实现 async/await
具体实现如下代码,解释的东西也放到代码里了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| function* generatorFn() { yield new Promise((resolve, reject) => { setTimeout(() => { resolve("完成异步请求1"); }, 1000); }); yield new Promise((resolve, reject) => { setTimeout(() => { resolve("完成异步请求2"); }, 1000); }); yield new Promise((resolve, reject) => { setTimeout(() => { resolve("完成异步请求3"); }, 1000); }); } assist(generatorFn);
function assist(fn) { let f = fn(); next(); function next(data) { let result = f.next(); console.log(result, `上一步请求的结果:${data}`); if (!result.done) { result.value.then((res) => { console.log(res); next(res); }); } } }
|
Promise 的方法
Promise.resolve()
返还一个 状态为 fulfilled
的 promise
对象
1 2 3
| console.log(Promise.resolve("解决"));
console.log(Promise.reject("未解决"));
|
Promise.all([])
all 方法第一个参数是数组,数组中放 promise 对象,当所有的 promise 对象都 resolve
了,才会执行 then()
,如果有一个是 reject 都拿不到结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| let p1 = new Promise((resolve, reject) => { setTimeout(() => { console.log(1111); resolve("我是1"); }, 2000); }); let p2 = new Promise((resolve, reject) => { setTimeout(() => { console.log(22222); resolve("我是2"); }, 3000); }); let p3 = new Promise((resolve, reject) => { setTimeout(() => { console.log(33333); resolve("我是3"); }, 1000); });
Promise.all([p1, p2, p3]).then((res) => { console.log("返回 resolve 中的结果,res 也是数组", res); });
|
Promise.race([])
跟 all 方法有些类似,传递一个数组,数组中的 promise 谁最先 resolve,res 就是谁。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| let p1 = new Promise((resolve, reject) => { setTimeout(() => { console.log(1111); resolve("我是1"); }, 2000); }); let p2 = new Promise((resolve, reject) => { setTimeout(() => { console.log(22222); resolve("我是2"); }, 3000); }); let p3 = new Promise((resolve, reject) => { setTimeout(() => { console.log(33333); resolve("我是3"); }, 1000); });
Promise.race([p1, p2, p3]).then((res) => { console.log(res); });
|
以上就是我的总结笔记啦,有用的话请点个赞吧,您的支持可就是我的动力啊,靴靴!