回顾一下 什么是同步和异步:
同步:“你当兵去吧!我不交别的男朋友!我一直等着你回来娶我!”
--专注做一个任务,等得到这个任务的结果后在执行其它任务,叫做同步;
异步:“你当兵去吧!你回来之前我会先找个其它男朋友,等你回来后再跟你聊我们的事”
--把任务分成2段,先执行一段,转而执行其它任务,等做好了准备,再回过头执行第二段,这种不连续的执行,就叫做异步;
一辈子很长,按我们人类的思路当然一辈子只对一段感情从一而终啦!
但是JavaScript却提倡使用异步方法执行(不要这么反人类吧!)
优点:因为可以利用时间队列机制解决无阻塞的运行应用!
缺点:异步操作会形成大量函数异步嵌套,难以维护问题...
对,你没听错,就是这么反人类,但我们也不是吃素的,总会有*代表月亮消灭你!
复制代码
异步的发展流程
文章开始先让我们看下异步的发展流程就是这个样子:
callback -> event -> Promise -> yield & co -> async await
让我们按照发现问题-解决问题-再发现问题-再解决问题的方式,来试试如何一步一步使代码变得优雅起来!
复制代码
买家:“小哥哥 给我来一个判断类型的方法”
卖家:“小姐姐 需求能满足,不过,我有高阶函数,要不要看看!”
买家:“what!?( ⊙ o ⊙ ) ” 高阶函数什么鬼
复制代码
函数作为一等公民,可以作为 参数 和 返回值 这种形式就是高阶函数
高阶函数至少满足以两个条件:
参数:接受一个或多个函数作为输入
返回值:输出一个函数
复制代码
预置函数
let isType = function(type,obj){
return Object.prototype.toString.call(obj) ===`[object ${type}]`
}
console.log(isType('Object',{}));
console.log(isType('Number',123));
console.log(isType('String','我最美'));
console.log(isType('String','hello'));
执行结果没毛病,都是true;--喜欢拿去!
但是问题来了,每次调用isType方法都需要传入类型,好麻烦啊!反人类!我要智能点的。
卖家说:针对此需求,我们做了升级哦!可以先批量产出可供调用的函数,您请看:
let isType = function(type){
return function(obj){
return Object.prototype.toString.call(obj) ===`[object ${type}]`;
}
}
let isObject = isType('Object');
let isString = isType('String');
console.log(isObject({}));
console.log(isString('我是美女'));
执行结果没毛病,都是true;而且,函数作为参数传递,并且函数作为返回值,必然高阶了! --喜欢拿去!
当达到某个条件时 执行callback: 条件:执行3次,打印-请你吃饭
function after(times,cb){
return function(){
if(--times === 0){
cb();
}
}
}
let eat = after(3,function(){console.log('请你吃饭')}); //这里函数当参数传是典型的callback回调
eat();
eat();
eat();
执行结果没毛病,打印了请你吃饭,用了回调又高阶;--喜欢拿去!
复制代码
callback
回调函数:
就是把任务的第二段单独写在一个函数里面,等到重新执行这个任务的时候,就直接调用这个函数。
既然今天异步主场,给大家介绍一个常见的node中的异步方法readFile:可以用来读取文件
例如这样:
fs.readFile(filename, function (err, data) {
if (err) throw err;
console.log(data);
});
错误优先:在node中,回调函数的第一个参数是错误对象(error-first callbacks)
用回调函数来解决异步问题:
function read(callback){
setTimeout(function(){
let result = '我是美女';
callback(result);
})
}
read(function(data){
console.log(data);
});
1)函数read执行时传了一个参数(参数是个函数)
2)setTimeout执行,这里没有写执行时间,系统默认是4秒
3)定义了一个result变量,callback函数执行,打印data
callback没有()是参数,后面加上()就是函数执行
复制代码
回调的问题
* 异步不支持try/catch,回调函数是在下一个事件环中取出,所以一般在回调函数的第一个参* 数预置错误对象(错误优先)
* 回调地狱问题,异步多级依赖的情况下嵌套非常深,代码难以阅读的维护
* 多个异步在某一时刻获取所有异步的结果
* 结果不能通过return返回
综上所述,项目中也是频频遇到,解决起来很头疼。怎么办呢!?嗳~~!咱们的主角Promise诞生了!
复制代码
Promise是个啥?
中译英了一下,Promise:承诺、许诺
————“你承诺我当兵回来会娶我!”
在程序中的意思就是:承诺我过一段时间后会给你一个结果。 什么时候会用到过一段时间?
答案是:异步操作
异步是指可能比较长时间才有结果的才做,例如网络请求、读取本地文件等...
复制代码
Promise解决了什么问题?
1) 解决-回调地狱 - 嵌套问题
2) 解决-并发异步,再同一时刻内获取并发的结果
3) 链式调用 (像jquery) A.b().c()...
每个promise上都有一个then方法
then方法种有两个参数:成功的函数,失败的函数
用来指定 Promise 对象状态改变时确定执行的操作
.then(resolve,reject)
参1 resolve 代表第一个函数(onFulfilled)
参2 reject 代表第二个函数(onRejected)
复制代码
Promise的三个状态
* Pending Promise对象实例创建时候的初始状态 ———— 装傻,什么也不做
* Fulfilled 可以理解为成功的状态 ———— 成功
* Rejected 可以理解为失败的状态 ———— 失败
怎么理解这三个状态呢?比如上面说到了“你承诺我当兵回来要娶我!”
1)我先要“等待”他当兵回来,做娶我的事情
2)如果娶了表示“成功”;如果没取表示“失败”;
当然也有可能一直让我等待一辈子...好可怜!
复制代码
动手来写一个Promise吧!
let promise = new Promise((resolve, reject) => {
console.log('hello');
});
console.log('美女'); // 1) hello 2) 美女
new Promise(()=>{}) 这里有一个参数:叫做执行器
1)是个回调函数
参1 resolve 代表第一个函数(onFulfilled)
参2 reject 代表第二个函数(onRejected)
2)会立即执行;
如上所示,先打印了执行器里的hello,再打印的美女,说明这个执行器是同步的;
复制代码
解决回调地狱
需求:读取第一个文件成功后,将值作为下一个读文件的入口内容,读文件,传文件...;
// 1.txt => 2.txt
// 2.txt => 哈哈哈 我是帅锅
let fs = require('fs');
function read(){
fs.readFile('./1.txt','utf8',function(err,data){
if(err) return console.log(err);
fs.readFile(data,'utf8',function(err,data){
if(err) return console.log(err);
console.log(data); // 我很帅
})
})
}
read();
实现:
1) 调用read函数
2) 异步读取1.txt文件,要求用utf8编码规范,执行回调函数
4) 错误优先,如果拿到的是个错误的值,调用并返回并打印错误值
5) 如果data无异常,将data作为下一个读文件的内容,继续2)流程,以此类推
//改写Promise形式
let fs = require('fs');
function read(file){
return new Promise(function(resolve,reject){
fs.readFile(file,'utf8',function(err,data){
if(err) return reject(err);
resolve(data);
})
})
}
read('./1.txt').then(function(data){
return read(data);
}).then(function(data){
console.log(data)
}).catch(function(err){
console.log(err)
});
1) 引入node的读取文件模块:fs模块
2) 调用read('./1.txt')函数并参(文件路径)
3) 遇到new Promise构造函数会立即执行
. 异步读取1.txt文件,要求用utf8编码规范,执行回调函数
错误优先,如果报错了,调用错误的函数,传入错误的原因
否则调用成功函数;
4) 返回promise的实例,才可以进行链式操作
* 同一个promise的实例可以多次.then().then().then()....
* 当成功后会将then中的成功方法按顺序执行
5) .then 时会调用成功方法,返回并从新从2)过程开始执行
*注意的是,这里read(data) 的data已经变成第一次读取的结果("./2.txt")作为形参传入
6) 经过2次的回调,最终打印出了 “哈哈哈 我是帅锅”
7) catch捕获错误,写在了最后,上面有err会走err 没有会走catch
无论有多少层回调都不用再一层嵌套一层!
复制代码
并发异步,再同一时刻内获取并发的结果
// 1.txt => template
// 2.txt => data
let fs = require('fs');
let result = {}
function out(key,data) {
result[key] = data;
if(Object.keys(result).length === 2){
console.log(result)
}
}
fs.readFile('./1.txt', 'utf8', function (err, data) {
if (err) return console.log(err);
out('template',data);
})
fs.readFile('./2.txt', 'utf8', function (err, data) {
if (err) return console.log(err);
out('data',data);
});
// 这种方式并不好,要声明全局的对象,并且成功的数量也是写死的,我们可以使用偏函数来进行改写
let fs = require('fs');
let result = {}
function after(times,cb) {
let result = {}
return function(key,data){
result[key] = data;
if(Object.keys(result).length === times){
cb(result)
}
}
}
let out = after(2,function(data){
console.log(data)
})
fs.readFile('./1.txt', 'utf8', function (err, data) {
if (err) return console.log(err);
out('template',data);
})
fs.readFile('./2.txt', 'utf8', function (err, data) {
if (err) return console.log(err);
out('data',data);
});
最后我们可以采用Promise.all方法来进行简化
let fs = require('fs');
function read(file){
return new Promise(function(resolve,reject){
fs.readFile(file,'utf8',function(err,data){
if(err) return reject(err);
resolve(data);
})
})
}
Promise.all([read('1.txt'),read('2.txt')]).then(([template,data])=>{
console.log({template,data})
});
// 不管两个promise谁先完成,Promise.all 方法会按照数组里面的顺序将结果返回
复制代码
Promise.race
接受一个数组,数组内都是Promise实例,返回一个Promise实例,这个Promise实例的状态转移取决于参数的Promise实例的状态变化。
当参数中任何一个实例处于resolve状态时,返回的Promise实例会变为resolve状态。
如果参数中任意一个实例处于reject状态,返回的Promise实例变为reject状态。
Promise.race([read('1.txt'),read('2.txt')]).then(data=>{
console.log({template,data})
},(err)=>{
console.log(err)
});
复制代码
Promise.reject
返回一个Promise实例,这个实例处于reject状态
Promise.reject('失败').then(data=>{
console.log(data);
},err=>{
console.log(err);
})
复制代码