一.基础知识巩固
- 同步(Synchronous): 代码依次向下执行,如果遇到请求获取其他的,等待执行完了,之后再执行后面的代码
- 异步(Asychronous): 代码依次向下执行,遇到异步的代码(事件、Ajax、setTimeout、setInterval、Promise 、Node…无需中断,,则继续执行后面的代码,等到他们请求完毕,在回调里面去执行他们)
- js单线程
- js执行顺序,主线程 > 异步(ajax,promise,fetch)> 队列(setTimeout/setInterval)
二.异步出现情况
- setTimeout/setInterval
- 元素绑定事件
- Ajax, Fetch
三.异步发展
-
回调函数(callback) [回调地狱问题,无限回调,维护空难]
-
事件监听(Listener)
-
观察者模式(发布订阅模式) DOM二级绑定多个事件(addEventListener) 原理就是观察者模式 ,事件池概念出来,jQ多个on
-
Promise类
-
Generator函数
-
Async 函数 (ES2017) 配合Proise使用 [暴爽]
四.回调函数 (将要执行的 任务放到一个函数里面,作为参数去传给另一个函数,另一个函数执行完毕,去执行这个函数)
- 简单,容易理解和 部署
- 不利于代码的阅读,和维护,各部分之间高度耦合,流程会很混乱,而且每一个任务只能指定一个回调函数。
eg1:
// ==>> callback
function fn(callback) {
var x = 1;
callback && callback(x); //函数fn里面的变量传给了他的回调里面
}
fn(function (val) {
console.log(val);
});
eg2:
// ==>> ajax callback
function fn(callback) {
$.ajax({
url: '...',
type: 'get',
success(data) {
if (data.result.code === 200) {
callback && callback(data.result);
}
}
});
}
fn(function (data) {
console.log(data);
});
eg3:
// ==>> 回调地狱
function getData() {
//1. 获取token
$.ajax({
url: '...',
success: function (data) {
if (data.code === 200) {
//2. 获取用户信息
$.ajax({
url: '...',
success: function () {
if (data.code === 200) {
//3.获取用户相关的新闻
$.ajax({
url: '...',
success: function () {
if (data.code === 200) {
...
}
}
});
}
}
});
}
}
});
}
getData();
五.事件监听(事件驱动模式) 自定义事件问题
- 任务的执行不取决代码的顺序,而取决于某一个事件是否发生。
- 监听函数有:on,bind,listen,addEventListener,attachEvent, observe(vue里面)
- 整个程序都要变成事件驱动型,运行流程会变得不清晰。
***绑定事件三种方式
-
行内绑定click
- 又包了一层,单击的时候,走的是里面,里面去调用你写的那个函数
-
给DOM对象绑定方法,方法相同就会有后面覆盖前面的问题 div1.onclick = function() {};
- 没有兼容性问题
- 因为是属性值,后面的会覆盖前面的,同一个事件同一个DOM元素只能绑定一次
-
DOM2二级事件 (addEventListener(eventName,fn, true/false)/attachEvent)
- 有兼容性问题
- 可以绑定多个,出来了一个<<事件池>>的概念,将事件push到里面,单击的时候,循环依次执行里面的函数
- jQ里面的on就是封装了DOM2二级事件
- 原理是发布订阅模式,将所有的事件放到一个事件池里面,循环依次去执行
发布订阅模式,也就是观察者模式
{'click': [fn1, fn2, fn3]} click执行 ===>循环一一 fn1() fn2() fn3()
// 发布emit 订阅on {女生失恋: ['哭', '购物', '吃']}
// {'入职':[setEmail, getPc], '失恋':[cry,shopping,eat]}
// on绑定事件
// emit 触发事件
// off 事件解绑
function Girl() {
this._event = {}; //自定义事件对象
}
Girl.prototype.on = function(eventName, callback) {
if (eventName && eventName.length && callback) {
if (this._event[eventName]) {
this._event[eventName].push(callback);
} else {
this._event[eventName] = [];
this._event[eventName].push(callback);
}
} else {
return null;
}
return this;
};
Girl.prototype.emit = function(eventName) {
if (eventName && eventName.length) {
this._event[eventName].forEach(element => {
element();
});
}
};
Girl.prototype.off = function(eventName, callback) {
if(eventName && eventName.length && callback) {
this._event[eventName].forEach((item, index) => {
if (item === callback) {
this._event[eventName].splice(index, 1);
return false;
}
});
}
return this;
};
function cry() {
console.log('哭');
}
function shopping() {
console.log('购物');
}
function eat() {
console.log('吃');
}
function setEmail() {
console.log('开通邮箱');
}
function getPc() {
console.log('获取办公用品');
}
const girl = new Girl();
girl.on('失恋', cry);
girl.on('失恋', shopping);
girl.on('失恋', eat);
girl.on('入职', setEmail);
girl.on('入职', getPc);
girl.emit('失恋');
girl.emit('入职');
girl.off('失恋', shopping);
girl.off('失恋', eat);
girl.emit('失恋');`
六.Promise对象
- 是一个类,类是有原型的Promise.prototype 里面有 then, catch, finally
- 是类,也有静态方法 Promise.resolve()/reject()/all()/race()
- 为了解决callback回调地狱的,它返回是一个promise实例,所以可以链式调用 (return this) then方法执行完了,返回一个promise对象
三种状态:(生命周期)
-
pendding 进行中
-
fulfilled 已成功 ==> resolve
-
rejected 已失败 ==> reject
pending ==> fulfilled
pending ==> rejected
***一旦状态改变,就不会再变,任何时候都可以得到这个结果, 返回promise实例,链式调用
*** Promise的构造函数中代码是同步执行的,但是then方法是异步执行的,then方法需要等到等到resolve函数执行时才得到执
*** then返回一个基本或则引用数据类型会自动调用Promise.resolve(val);进行转为promise对象,给到下一个then,如果你抛出一个错误,这下一个catch会捕获到
基本语法
let promsie1 = new Promise((resolve, reject) => {
resolve; 成功
reject;失败
});
promise instanceof Promise; // ==> true;
promise instanceof Object; // ==> true;
// ==>查看类的属性和方法(原型上,静态方法,私有属性)
console.dir(Promise.prototype);
console.dir(Promise);
console.dir(promise1);
//==>返回Promise实例, then里面可以写两个函数,代表resolve, reject,但是我们一般捕获错误,不那么写,用catch
promise1.then(data => {
console.log(data);
}).catch(err => {
console.log(err);
});
1.Promise.resovle();
- 1.将现有的东西转成一个promise
- 2.变成resolve状态,成功状态
let promise1 = Promise.resolve(100);
promise1.then(data => {
console.log(data); //==> 100;
});
等价于下面的
let promsie1 = new Promise(resolve =>{
resolve(100);
});
promise1.then(data => {
console.log(data); //==> 100;
});
let x = 2;
let p1 = new Promise((resolve, reject) => {
if (x >=1) {
resolve(x);
} else {
reject('error')
}
});
p1.then(res => {
// return res; // ====>> 返回一个新的Promise实例,进行链式调用
return Promise.resolve(res);
}).then(res => {
console.log(res);
}).then(res => {
console.log(111);
});
2.Promise.reject();
- 1.将现有的东西转成一个promise
- 2.变成reject状态,成功状态
let promise1 = Promise.reject(100);
promise1.then(data => {
console.log(data); //==> 100;
});
等价于下面的
let promsie1 = new Promise((resolve, reject) =>{
reject(100);
});
promise1.then(data => {
console.log(data);
}).catch(err => {
console.log(data); //==> 100;
});
3.Promise.all([p1, p2, p3…]);
场景: 当首页的数据都请求完毕再进行展示出来,就用到了
- 把promise打包,扔到一个数组里面,打包完成还是一个promise对象
- 返回一个数组,里面有他们依次执行resolve的值, 用数组结构去接收 let [result1,result] = res;
- 如果有一个失败了,则就走到catch里面了
- 只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
- 只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数catch里面
eg1:
let p1 = Promise.resolve(100);
let p2 = Promise.resolve(200);
let promise2 = Promise.all([p1, p2]);
promise2.then(data => {
let [result1, result2] = data;
console.log(result1, result2); // ==> 100, 200
}).catch(err => {
console.log(err);
});
eg2:
let p1 = Promise.resolve(100);
let p2 = Promise.reject(200);
let promise2 = Promise.all([p1, p2]);
promise2.then(data => {
let [result1, result2] = data; // ==> 数组解构赋值
console.log(result1, result2); // ==> 100, 200
}).catch(err => {
console.log(err);
});
4.Promise.race([p1, p2, p3…]);
场景: 先来先得
- 把promise打包,扔到一个数组里面,打包完成还是一个promise对象
- 哪个率先改变的Promise状态,则实例的返回值,就传递给回调函数。 成功传个then,失败传给catch
let p1 = Promise.reject('1');
let p2 = Promise.reject('2');
let p3 = Promise.resolve('3');
let p = Promise.race([p1, p2, p3]).then(res => {
console.log(res);
}).catch(err => {
console.log(err); //==> 1
});
5.Promise.prototype.then()
- 是原型上的方法
- resolve的时候去回调then
- 返回一个新的Promise实例,链式写法,可以then
let getToken = function() {
...
};
let getUserInfo = function() {
...
};
let p1 = new Promise((resolve, reject) => {
getToken();
});
p1 instanceof Promise; // ==> true
eg2 ================>> : 回家继续深入
let p1 = new Promise((resolve, reject) => {
resolve('success...');
}).then(data => {
console.log(data);
return 100; // => return Promise.resolve(100); 没有return都可以吗?
}).then(data => {
console.log(data);
return 200; // // => return Promise.resolve(200);
}).then(data => {
console.log(data);
});
success, 100, 200
6.Promise.prototype.catch()
- 是原型上的方法
- resolve的时候去回调catch(捕获异常)
- 返回一个新的Promise实例,链式写法,可以then
- 虽然then可以写俩个函数,但是我们一般用catch去进行捕获
7.Promise.prototype.finally()
- 是原型上的方法
- 不管是resolve,还是reject都去执行finally,类似于 try {} catch(e) {} finally{}
- 返回一个新的Promise实例,链式写法,可以then
8.Promise面试题
eg1: Promise的构造函数中代码是同步执行的,但是then方法是异步执行的,then方法需要等到等到resolve函数执行时才得到执行。
https://baijiahao.baidu.com/s?id=1584286066043336871&wfr=spider&for=pc
const promise = new Promise((resolve, reject) => {
console.log(1);
resolve();
console.log(2);
});
promise.then(() => {
console.log(3);
});
console.log(4);
// ==> 1 2 4 3
eg2: Promise一旦执行了resolve函数后,就不会再执行reject和其他的resolve函数了。一旦Promise执行了reject函数,将会被catch函数捕获,执行catch中的代码。
const promise = new Promise((resolve, reject) => {
resolve('success1');
reject('error1');
resolve('success2');
reject('error2');
});
promise.then(res => {
console.log(res);
}).catch(err => {
console.log(err);
});
//==> successs1
eg3: 题目中Promise首先resolve(1),接着就会执行then函数,因此会输出1,然后在函数中返回2。
注意: return 返回基本数据类型,其实就是 Promise.resolve(value), 如果是返回引用数据类型会冲掉,返回这个对象
const p1 = Promise.resolve(1).then(res=> {
console.log(res);
return 2; // ==>> return Promise.resolve(2);
return Promise.resolve({
a: 1,
b: 2
});
return Promise.reject('error');
}).catch(err => {
console.log(err);
}).then(res=> {
console.log(res);
}).catch(err => {
console.log(err);
});
p1 instanceof Promise; // ==> true
eg4: 经典 setTime/promise (https://blog.csdn.net/baidu_33295233/article/details/79335127)
//==> 定时器是队列
setTimeout(function(){
console.log(1);
}, 0)
// ==>>执行
new Promise(function executor(resolve){
console.log(2);
for(var i = 0; i < 1000; i++){
i = 9999 && resolve(); // ==>> 放到主线程后面
}
console.log(3);
}).then(function(){
console.log(4);
})
console.log(5);
2 3 5 4 1
详解:
之前说过,在定时器,事件,ajax等操作的时候,会使一个异步操作,会把该操作放到一个task queue里,需要等当前主线程的任务完成后,会读取任务队列(task queue)中的是事件。
那么,setTimeout会放到任务队列中,代码继续往下走。
所以先输出2 3。
promise中的then操作是放在<<执行栈>>,也就是主线程的最后。
那么主线程会继续往下走咯。
所以输出 5 4
最后主线程的任务搞完了,才会去执行task queue中的任务。
所以最后执行1
setTimeout(() => {
console.log(1)
}, 0);
let promse1 = new Promise((resolve, rject) => {
console.log(2);
resolve('ok');
console.log(3);
});
promse1.then(res => {
console.log(4);
}).catch(err => {
console.log(err);
});
console.log(5);
七、Generator函数 (生成器) Generator yield next
- 解决异步,深度嵌套问题
1.语法:
function *show{
yield
}
function* show {
yield
}
2.用法: 配合next关键字使用,返回yiled后面的值
function * fn() {
yield 1;
yield 2;
yield 3;
return 'ok';
}
let f = fn();
console.log(f);
Object.prototype.toString.call(f); //==> "[object Generator]"
f.next(); // ==> {value: 1, done:false}
f.next(); // ==> {value: 2, done:false}
f.next(); // ==> {value: 3, done:false}
f.next(); // ==> {value: 'ok', done:true}
f.next(); // ==> {value: undefined, done:true} 完成了
可以使用f.next.value; // ==> 拿出来想要的值
3.自动输出 迭代器 for - of
- return后面的东西遍历出不来,遍历的是yield后面的东西
for (let key of f) {
console.log(key); // ==> 1, 2, 3
}
4.Generator配合解构赋值使用
let [a, b, c, d] = fn();
console.log(a, b, c, d); // ==> 1, 2, 3, undefined 将yield后面的值放到一个数组里面了
5.Generator配合拓展运算符使用
let [a, ...b] = fn();
console.log(a, b); // ==> 1, [2, 3] 将yield后面的值放到一个数组里面了
console.log(...fn(); // ==> 1, 2, 3
6.Generator配合Array.from转为数组
let f = Array.from(fn()); //==> [1, 2, 3]
console.log([...fn()]);
7.Generator配合axios请求数据
function * fn() {
let val = yield '3653223131';
yield axios.get(`http//sfddd/${token}`);
}
let f = fn(); // ==>>出来一个Generator实例
let userName = f.next().value; // ==>>拿到第一个yield的的值
f.next(userName).value.then(res => { // ==>>将第一个的值传递给URL里面的token, axios返回promise对象
console.log(res);
});
八、async函数 async(异步)
1.语法:
async function fn() { // >表示异步,这个函数里面有异步任务
let result = await xxx //>表示后面结果需要等待
}
2.特点:
-
await 只能放到async函数中 (yield只能用在Generator函数中) 但是不是必须的
-
相比Generator更加语义化
-
await 后面可以是promise对象,也可以是number,string, boolean,自动转为promise对象
-
async函数执行完毕返回promsie对象 默认不写return的话, 所以还可以继续then执行下一步操作
-
只要await语句后面的Promise状态变为reject,那么整个async函数就会中断执行 (优化使用try-catch)
-
async函数可以没有await指令和return,都没有返回promise对象, 有return没有await返回Promise.resolve(val);
-
async抛出错误,如何解决 ? 只有promise就要进行catch
- 采用try - catch进行捕获异常
- Promise进行catch捕获
- 捕获异常,代码更加健壮
-
async函数完全可以看作多个异步操作,包装成的一个 Promise对象,而await命令就是内部then命令的语法糖。resolve成了,数据返回到then里面,在传给前面定义的变量
-
await命令后面是一个 Promise 对象。如果不是,会被转成一个立即resolve的 Promise 对象。 Promies.resolve();
eg1: 返回promise对象,没有await情况
async function fn() {
return 'async'; // ==> return Promise.resolve('async');
}
var f = fn();
f instanceof Promise; // ==> true
f.then(res =>{
console.log(res); // ==> Promise{<resolved>: undefined}****
}).catch(err =>{
console.log(err);
});
eg2:没有return和await的情况,
async function fn() {
}
var f = fn();
console.log(f instanceof Promise); // ==> true
f.then(res =>{
console.log(res); // ==> Promise{<resolved>: undefined}
}).catch(err =>{
console.log(err);
});
eg3: 有一个reject值不执行了,停止了
async function fn() {
await Promise.reject('fail');
// ==>>下面代码都不执行了
await Promise.resolve('success');
console.log(1)
}
var f = fn();
f.then(res => {
console.log(res);
}).catch(err => {
console.log(err); // ==> fail
});
1.try - catch进行捕获
async function fn() {
try() {
await Promise.reject('fail');
} catch(e){
}
// ==>>下面代码都不执行了
await Promise.resolve('success');
console.log(1)
}
var f = fn();
f.then(res => {
console.log(res);
}).catch(err => {
console.log(err); // ==> fail
});
2.Promise的catch进行捕获
async function fn() {
await Promise.reject('fail');
// ==>>下面代码都不执行了
await Promise.resolve('success');
console.log(1)
}
var f = fn();
f.then(res => {
console.log(res);
}).catch(err => {
console.log(err); // ==> fail
});
eg4: 读取多个ajax,没有关系的情况下
const fs = require('fs');
// ==> 封装读取文件函数
function readFile(file) {
return new Promise((resolve, reject) => {
fs.readFile(file, (err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
}
})
});
}
async function fn() {
// ==> 三个执都resolve了,才去对应的执行赋值, 配合解构赋值
let [a, b, c] = await Promise.all([
'./a.txt',
'./b.txt',
'./c.txt'
]);
console.log(a.toString());
console.log(b.toString());
console.log(c.toString());
}
fn();
案例,用node的fs模块读取文件分别用callback, promise, generator,async进行对比
===> 分别有三个文本文件 a.txt、 b.txt、 c.txt进行读取里面的内功,并且依次输出
1.callback进行读取
const fs = require('fs'); //==> 导入fs模块
function fn1(callback) {
fs.readFile('./a.txt', (err, data) => {
if (err) {
console.log(err);
return;
} else {
console.log(data.toString());
callback && callback(() => {
fs.readFile('./c.txt', (err, data) => {
if (err) {
console.log(err);
return;
} else {
console.log(data.toString());
}
})
});
}
});
}
fn1((callback) => {
fs.readFile('./b.txt', (err, data) => {
if (err) {
console.log(err);
return;
} else {
console.log(data.toString());
callback && callback();
}
});
});
// 2.promise读取文件
function readFile(file) {
return new Promise((resolve, reject) => {
fs.readFile(file, (err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
}
})
});
}
readFile('./a.txt').then(res => {
console.log(res.toString());
return readFile('./b.txt');
}).catch(err => {
console.log(err);
}).then(res => {
console.log(res.toString());
return readFile('./c.txt');
}).catch(err => {
console.log(err);
}).then(res => {
console.log(res.toString());
}).catch(err => {
console.log(err);
});
3.Generator读取文件
const fs = require('fs');
function readFile(file) {
return new Promise((resolve, reject) => {
fs.readFile(file, (err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
}
})
});
}
function *gen() {
yield readFile('./a.txt');
yield readFile('./b.txt');
yield readFile('./c.txt');
}
let f = gen(); //==>>先执行Generator返回迭代器
//==> console.log(s = f.next().value); Promise对象 状态是pending
var s = f.next().value.then(res => { // f.next去执行 readFile('./a.txt'); 返回一个Promise实例进行then和catch
console.log(res.toString());
return f.next().value;
}).catch(err =>{
console.log(err);
}).then(res =>{
console.log(res.toString());
return f.next().value;
}).catch(err =>{
console.log(res);
}).then(res =>{
console.log(res.toString());
}).catch(err =>{
console.log(res);
});
4.async函数读取文件
const fs = require('fs');
function readFile(file) {
return new Promise((resolve, reject) => {
fs.readFile(file, (err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
}
})
});
}
async function gen() {
// ==> 捕获异常,代码更加健壮
try {
let file1 = await readFile('./a.txt');
console.log(file1.toString());
let file2 = await readFile('./b.txt');
console.log(file2.toString());
let file3 = await readFile('./c.txt');
console.log(file3.toString());
} catch(e) {
}
}
var s = gen();
console.log(s); // ==> promise对象
5.Promise.all方法去读取,三个都成功了 resolve,状态才进行返回,一个失败了直接catch (这是没有关系哦!!!三个文件顺序的情况下)
const fs = require('fs');
function readFile(file) {
return new Promise((resolve, reject) => {
fs.readFile(file, (err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
}
})
});
}
Promise.all([readFile('./a.txt'), readFile('./b.txt'), readFile('./c.txt')]).then(res =>{
let [f1, f2, f3] = res;
console.log(f1.toString(), f2.toString(), f3.toString());
}).catch(err => {
console.log(err);
});
读取文件:
const fs = require('fs');
class File {
static readFile(fileName) {
if (fileName && fileName.length) {
let promise = new Promise((resolve, reject) => {
fs.readFile(fileName, 'utf-8', (err, data) => {
if (err) {
console.log(err);
}
resolve(data);
});
});
return promise;
}
}
}
// 1. Promies读取文件
Promise.all([
File.readFile('./b'),
File.readFile('./test')
]).then(res => {
let [a, b] = res;
console.log(a);
console.log(b);
});
//2. async配合Promise读取文件
const asyncReadFile = async function() {
// 2.1分开写
let a = await File.readFile('./b');
console.log(a);
let b = await File.readFile('./test');
console.log(b);
// 2.2 Promise.all()写到一块
let [x, y] = await Promise.all([File.readFile('./b'), File.readFile('./test')]);
console.log(x);
console.log(y);
return 10;
};
let s = asyncReadFile();
console.log(s);