所谓"异步",简单说就是一个任务分成两段,先执行第一段,然后转而执行其他任务,等做好了准备,再回过头执行第二段,比如,在我们烧水时可以干很多事情,当水烧开后在用水洗脸。这种不连续的执行,就叫做异步。相应地,连续的执行,就叫做同步。例如在烧水的过程中我们一直等待水烧开而不去干别的事情。
1.高阶函数
函数作为一等公民,可以作为参数和返回值
高阶函数至少满足以两个条件
- 接受一个或多个函数作为输入
- 输出一个函数
1.1 预置参数
let isType = function(type,obj){
return Object.prototype.toString.call(obj) ===`[object ${type}]`
}
console.log(isType('Object',{}));
console.log(isType('Object',{}));
console.log(isType('String','hello'));
console.log(isType('String','hello'));
// 我们发现每次调用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(isObject({}));
1.2 预置函数
function after(times,cb){
return function(){
if(--times === 0){
cb();
}
}
}
let eat = after(3,function(){console.log('吃完了')});
eat();
eat();
eat();
// 当调用次数达到我们预置的次数时,执行我们预置的函数
到此我们对函数又有了进一步的认识,下面我们就来开始进入异步的解决方案。
2.回调函数
所谓回调函数,就是把任务的第二段单独写在一个函数里面,等到重新执行这个任务的时候,就直接调用这个函数。我们介绍一个常见的node中的异步方法readFile可以用来读取文件。
fs.readFile(filename, function (err, data) {
if (err) throw err;
console.log(data);
});
在node中回调函数的第一个参数是错误对象(error-first callbacks)
2.1 回调函数的应用
function read(callback){
setTimeout(function(){
let result = 'zpfx';
callback(result);
})
}
read(function(data){
console.log(data);
});
我们可以利用回调函数来解决异步问题
3.回调的问题
- 异步不支持try/catch,回调函数是在下一事件环中取出,所以一般在回调函数的第一个参数预置错误对象
- 回调地狱问题,异步多级依赖的情况下嵌套非常深,代码难以阅读的维护
- 多个异步在某一时刻获取所有异步的结果
- 结果不能通过return返回
4.Promise
Promise本意是承诺,在程序中的意思就是承诺我过一段时间后会给你一个结果。 什么时候会用到过一段时间?答案是异步操作,异步是指可能比较长时间才有结果的才做,例如网络请求、读取本地文件等
4.1 Promise的三种状态
例如媳妇说想买个包,这时候他就要"等待"我的回复,我可以过两天买,如果买了表示"成功",如果我
最后拒绝表示"失败",当然我也有可能一直拖一辈子
- Pending Promise对象实例创建时候的初始状态
- Fulfilled 可以理解为成功的状态
- Rejected 可以理解为失败的状态
then 方法就是用来指定Promise 对象的状态改变时确定执行的操作,resolve 时执行第一个函数
(onFulfilled),reject 时执行第二个函数(onRejected)
4.2 构造Promise
promise的方法会立刻执行
let promise = new Promise(() => {
console.log('hello');
});
console.log('world');
// hello
// world
4.3 promise也可以代表一个未来的值
const fs = require('fs');
let promise = new Promise((resolve, reject) => {
fs.readFile('./content.txt', 'utf8', function (err, data) {
if (err) return reject(err);
resolve(data);
})
});
promise.then(data => {
console.log(data);
});
promise.then(data => {
console.log(data);
});
// 一个promise实例可以多次调用then当成功后会将结果依次执行
4.4 代表一个用于不会返回的值
const fs = require('fs');
let promise = new Promise((resolve, reject) => { });
promise.then(data => {
console.log(data); //代表一个用于不会返回的值
});
4.5 实现买包的案例
function buyPack() {
return new Promise((resolve, reject) => {
setTimeout(function () {
var random = Math.random();
if (random > 0.5) {
resolve('买');
} else {
resolve('不买');
}
}, 2000)
})
}
buyPack().then(data => {
console.log(data);
}, data => {
console.log(data);
});
4.6 Error会导致触发Reject
可以采用then的第二个参数捕获失败,也可以通过catch函数进行
function buyPack() {
return new Promise((resolve, reject) => {
throw new Error('没钱')
})
}
buyPack().then(data => {
console.log(data);
}, data => {
console.log(data);
});
5.解决回调地狱
回归正题,先用promise解决第一个问题"回调地狱"
// 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();
改写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)
});
当第一个then中返回一个promise,会将返回的promise的结果,传递到下一个then中。这就是比较著名的链式调用了。
6.同步异步的结果
我们将多个异步请求的结果在同一时间进行汇总
// 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 方法会按照数组里面的顺序将结果返回
7.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)
});
8.Promise.resolve
返回一个Promise实例,这个实例处于resolve状态
Promise.resolve('成功').then(data=>{
console.log(data);
});
9.Promise.reject
返回一个Promise实例,这个实例处于reject状态
Promise.reject('失败').then(data=>{
console.log(data);
},err=>{
console.log(err);
})
下一节我们会继续介绍generator和async/await的用法,以及Promise的实现原理,喜欢的点个赞吧_!
支持我的可以给我打赏哈
网友评论