异步编程简述:
- 无论是在浏览器环境中还是在node环境中,我们都会使用JavaScript
- 完成各种异步操作,如在浏览器环境中的定时器、事件、ajax等或是node环
- 境中的文件读取、事件等。伴随着异步编程的就是回调机制(复习jQuery)。
- 明确一点异步编程避免不了回调。
let obj = {};
let fs = require('fs');
fs.readFile('./data/name.txt', 'utf-8', (err, data) => {
obj.name = data;
Store.fire(obj);
});
fs.readFile('./data/number.txt', 'utf-8', (err, data) => {
obj.number = data;
Store.fire(obj);
});
fs.readFile('./data/score.txt', 'utf-8', (err, data) => {
obj.score = data;
Store.fire(obj);
});
let Store = {
list: [],
times: 3,
subscribe (func) {
this.list.push(func);
},
fire (...args) {
--this.times == 0 && this.list.forEach(ele=>{
ele.apply(null, args);
})
}
}
Store.subscribe(show);
function show (data) {
console.log(data);
}
复制代码
异步编程问题:
- 产生回调地狱,难于维护和扩展。
- try catch只能捕获同步代码中出现的异常。
- 同步并发的异步存在一定的问题。
解决方案:
- ES6 Promise可以解决回调地狱、以及同步并发的异步问题。(异步操作
- 的异常捕获有其他方式解决。)
- 但依旧会有明显的回调痕迹,之后学习ES6的generator 、ES7的
- async await争取让异步代码看起来和同步一样。写起来更优雅一些。
Promise(.then)
微任务和宏任务
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
Promise.resolve().then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
});
console.log('script end');
复制代码
打印顺序是什么?
正确答案是: ==script start, script end, promise1, promise2, setTimeout==
- 每个线程都会有它自己的event loop(事件循环),所以都能独立运行。然而所有同源窗口会共享一个event loop以同步通信。event loop会一直运行,来执行进入队列的宏任务。一个event loop有多种的宏任务源(译者注:event等等),这些宏任务源保证了在本任务源内的顺序。但是浏览器每次都会选择一个源中的一个宏任务去执行。这保证了浏览器给与一些宏任务(如用户输入)以更高的优先级。好的,跟着我继续……
宏任务(task)
- 浏览器为了能够使得JS内部task与DOM任务能够有序的执行,会在一个task执行结束后,在下一个 task 执行开始前,对页面进行重新渲染 (task->渲染->task->...)
- 鼠标点击会触发一个事件回调,需要执行一个宏任务,然后解析HTMl。还有下面这个例子,setTimeout
- setTimeout的作用是等待给定的时间后为它的回调产生一个新的宏任务。这就是为什么打印‘setTimeout’在‘script end’之后。因为打印‘script end’是第一个宏任务里面的事情,而‘setTimeout’是另一个独立的任务里面打印的。
微任务(Microtasks )
-
微任务通常来说就是需要在当前 task 执行结束后立即执行的任务,比如对一系列动作做出反馈,或或者是需要异步的执行任务而又不需要分配一个新的 task,这样便可以减小一点性能的开销。只要执行栈中没有其他的js代码正在执行且每个宏任务执行完,微任务队列会立即执行。如果在微任务执行期间微任务队列加入了新的微任务,会将新的微任务加入队列尾部,之后也会被执行。微任务包括了mutation observe的回调还有接下来的例子promise的回调。
-
一旦一个pormise有了结果,或者早已有了结果(有了结果是指这个promise到了fulfilled或rejected状态),他就会为它的回调产生一个微任务,这就保证了回调异步的执行即使这个promise早已有了结果。所以对一个已经有了结果的promise调用.then(yey, nay)会立即产生一个微任务。这就是为什么‘promise1’,'promise2'会打印在‘script end’之后,因为所有微任务执行的时候,当前执行栈的代码必须已经执行完毕。‘promise1’,'promise2'会打印在‘setTimeout’之前是因为==所有微任务总会在下一个宏任务之前全部执行完毕。==
.then的链式操作
- 上一个不抛出错误的话, 那下一个then会默认执行成功回调函数, 抛出错误的话会执行失败回调函数
- 上一个then的返回至会作为下一个then注册函数的执行参数
- 上一个then返回一个Promise对象的话,后面的then就会成为这个对象的注册函数
// resolve 和 resolve是互斥事件
let oP = new Promise((resolve, reject)=> {
// 在构造函数中同步执行
console.log('123');
reject('aaa');
resolve('ddd');
});
oP.then((data)=> { // 被当作微任务
// 异步执行
// setTimeout 是宏任务
// 宏任务 微任务
// task queue 1 task queue 2
// 微任务有优先执行权
console.log(data);
return 20;
}, (reason)=>{
console.log(reason);
return 30;
}).then((data)=> {
console.log('ok then2 ' + data);
}, (reason)=> {
console.log('no then2 ' + reason);
});
// .then的链式操作
// 上一个不抛出错误的话, 那下一个then会默认执行成功回调函数, 抛出错误的话会执行失败回调函数
// 上一个then的返回至会作为下一个then注册函数的执行参数
// 上一个then返回一个Promise对象的话,后面的then就会成为这个对象的注册函数
复制代码
用解决回调地狱
回调地狱
// name.txt: ./data/number.txt
// number.txt: ./data/score.txt
// score.txt: 100
let fs = require('fs');
fs.readFile('./data/name.txt', 'utf-8', (err, data)=> {
console.log(data);
if(data) {
fs.readFile(data, 'utf-8', (err, data)=> {
console.log(data);
if(data) {
fs.readFile(data, 'utf-8', (err, data)=> {
console.log(data);
});
}
});
}
});
复制代码
使用Promise后
// name.txt: ./data/number.txt
// number.txt: ./data/score.txt
// score.txt: 100
let fs = require('fs');
function readFile(path) {
return new Promise((resolve, reject) => {
fs.readFile(path, 'utf-8', (err, data) => {
if (data) {
resolve(data);
}
});
});
}
readFile('./data/name.txt').then((data) => {
console.log(data);
return readFile(data);
}).then((data) => {
console.log(data);
return readFile(data);
}).then((data) => {
console.log(data);
});
// node运行结果
// ./data/number.txt
// ./data/score.txt
// 100
复制代码
.then .catch .finally
// 链式操作当遇到空的.then时,就当它不存在
let oP = new Promise((res, rej) => {
res();
});
oP.then(() => {
throw new Error('www');
}).then(() => {
}).catch((err)=>{
console.log(err);
// throw new Error('dasf');
return 10;
}).then((val)=>{
console.log('catch after ok:' + val); // 输出
},(reason)=>{
console.log('catch after no:' + reason) // 解开catch报错注释的时候输出
}).finally(()=>{
console.log('over');
});
复制代码
Promise.all()获取并发进程的结果 Promise.race() 赛跑
-
Promise.all可以将多个Promise对象实例包装成一个新的Promise实例
-
同时,成功和失败的返回至使不同的,成功的时候返回的是一个结果数组失败的时候则返回最先被reject失败状态的值
// 在数组中的所有异步进程都成功执行后再回执行.then后的resolve 有一个进程不成功都只会执行reject
Promise.all([readFile('./data/number.txt'), readFile('./data/name.txt'), readFile('./data/score.txt')]).then((val) => {
console.log(val);
}, (reason)=> {
console.log('no');
})
复制代码
- 顾名思义, Promise.race就是赛跑的意思,意思就是说,Promise.race([p1,p2,p3])里面那个结果获得的快,就返回那个结果,不管结果本身世成功状态还是失败状态
// race 赛跑 数组中的线程谁先成功 后面的then参数就是谁的返回值
Promise.race([readFile('./data/number.txt'), readFile('./data/name.txt'), readFile('./data/score.txt')]).then((val) => {
console.log(val);
}, (reason)=> {
console.log('no');
})
复制代码
MyPromise
执行
let oP = new MyPromise((res, rej) => {
res('ee');
});
oP.then((val)=> {
console.log(val,'ok')
return new MyPromise((res, rej)=>{
res(0);
});
},(reason)=>{
console.log(reason,'no');
return 12;
}).then(val=> {
console.log(val);
});
复制代码
MyPromise文件
function MyPromise(executor) {
var self = this;
self.status = 'pending'; // 状态
self.resolveValue = null;
self.rejectReason = null;
self.resolveCallBacksList = []; // 实现在执行函数中可以使用异步例如setTimeout
self.rejectCallBacksList = []; //
function resolve(value) {
if (self.status === 'pending') {
self.status = 'Fulfilled';
self.resolveValue = value;
self.resolveCallBacksList.forEach((ele) => {
ele();
});
}
}
function reject(reason) {
if (self.status === 'pending') {
self.status = 'Rejected';
self.rejectReason = reason;
self.rejectCallBacksList.forEach((ele) => {
ele();
});
}
}
// 报错时的处理方法 所有执行用 try catch 包裹
try {
// 执行我们在外界传入的函数, 将对象里的resolve, reject函数传进去, 在new一个新的Promise时, 自己定义的名字只是用来接收 有点绕?
executor(resolve, reject);
} catch (e) {
reject(e);
}
};
// 如果then的返回值是对象时的处理方式
function ResolutionReturnPromise(nextPromise, returnValue, res, rej) {
if(returnValue instanceof MyPromise) {
// Promise对象
returnValue.then(function (val) {
res(val);
}, function (reason) {
rej(reason);
})
}else {
res(returnValue);
}
}
MyPromise.prototype.then = function (onFulfilled, onRejected) {
var self = this;
// 判断注册函数是否为空, 如果是true就返回以下函数 将返回值作为下一个then的参数
if (!onFulfilled) {
onFulfilled = function (val) {
return val;
}
}
if (!onRejected) {
onRejected = function (reason) {
return new Error(reason);
}
}
// 链式操作? new一个新的Promise时,里面的代码是同步执行的
var nextPromise = new MyPromise(function (res, rej) {
if (self.status === 'Fulfilled') { // 判断状态 下同
setTimeout(() => {
try {
var nextResolveValue = onFulfilled(self.resolveValue); // 执行注册函数并接收返回值
ResolutionReturnPromise(nextPromise, nextResolveValue, res, rej);
} catch (e) {
rej(e);
}
}, 0);
}
if (self.status === 'Rejected') {
setTimeout(() => {
try {
var nextRejectReason = onRejected(self.rejectReason);
ResolutionReturnPromise(nextPromise, nextRejectReason, res, rej);
} catch (e) {
rej(e);
}
}, 0);
}
if (self.status === 'pending') {
if (onFulfilled) {
self.resolveCallBacksList.push(function () {
setTimeout(() => {
try {
var nextResolveValue = onFulfilled(self.resolveValue);
ResolutionReturnPromise(nextPromise, nextResolveValue, res, rej);
} catch (e) {
rej(e);
}
}, 0);
});
}
if (onRejected) {
self.rejectCallBacksList.push(function () {
setTimeout(() => {
try {
var nextRejectReason = onRejected(self.rejectReason);
ResolutionReturnPromise(nextPromise, nextRejectReason, res, rej);
} catch (e) {
rej(e);
}
}, 0);
});
}
}
});
return nextPromise; // 返回一个Promise对象
}
MyPromise.all = function (promiseArr) {
return new MyPromise(function (resolve, reject) {
if(!Array.isArray(promiseArr)) {
return reject(new TypeError("argument must be anarray"));
}
var promiseArrValue = [];
promiseArr.forEach((promise, index) => {
promise.then((val)=>{
promiseArrValue.push(val);
if(index == promiseArr.length - 1) {
return resolve(promiseArrValue);
}
}, (reason)=>{
reject(reason);
})
});
});
}
复制代码