异步简单来讲就是作一件事时,作到一半可能须要等待系统或服务处理以后才会获得响应和结果,此时能够转去作另外一件事,等到得到响应以后在去执行剩下一半的事情。反之同步就是一直等到响应而后接着作事,中间不会跳去作别的事。es6
异步发展史能够简单概括为: callback -> promise -> generator + co -> async+await(语法糖)npm
callback也就是咱们常常听到的回调函数,它会在咱们异步任务执行并收到结果响应后出发,举个简单的例子:数组
let fs = require('fs');
fs.readFile('./2.promise/1.txt', 'utf8', function(err, data) {
fs.readFile(data, 'utf8', function(err,data) {
console.log(data);
});
});
复制代码
(这里有个点就是异步并不支持try/catch,只有同步方可)promise
callback虽然帮咱们解决了异步问题,可是它仍有一些不足,首先,试想若是上面的嵌套一多,在代码上看起来就会很乱,以后回来修改逻辑时就会很难入手,一旦修改了一个,它嵌套的回调函数也要跟着改,第二个问题就是没法合并两个或多个异步的结果,譬如如下例子:bash
fs.readFile('./2.promise/1.txt', 'utf8', function(err,data) {
});
fs.readFile('./2.promise/2.txt', 'utf8', function(err, data) {
console.log(data);
});
复制代码
Promise的引入就解决了以上这些问题,首先来看下Promise的简单用法:并发
let p = new Promise(function(resolve, reject) {
resolve(100);
});
p.then(function(data) {
console.log('data', data);
}, function(err) {
console.log('err', err);
});
复制代码
能够看到Promis经过then的链式调用解决了嵌套回调的问题,在用法上Promise的构造函数会接受一个executor函数,这个函数带有两个参数resolve和reject,两个参数背后其实就是两个函数,而经过Promise构造函数建立出来的对象会保存一个status属性,resolve会作的事就是将这个属性从初始化的pending转为resolved,而reject则是转为rejected,同时两个函数均可以接受一个参数,做为以后then中回调函数的参数传入,那么在then方法中咱们能够看到它接收两个参数,第一个就是成功resolved以后会调用的回调函数,第二个就是rejected的回调函数。异步
这里注意的是,只要状态转为resolved或rejected之中的其中一个,那么当前promise对象就不能再转变状态了。以后无论调resolve仍是reject都会被忽略。async
另外,上面所说Promise是能够支持链式调用的,因此then是能够屡次调用的,可是由于刚刚所说状态不可转变的问题,因此链式调用每次then返回的不是当前的Promise对象而是一个新的Promise对象,那么第2次then的状态又是怎么决定的呢,第一次then中不管是成功的回调仍是失败的回调只要返回告终果就会走下一个then中的成功,若是有错误走下一个then的失败。函数
接下来介绍一些其余Promise的使用方法:ui
- Promise.all
const p = Promise.all([p1, p2, p3]);
复制代码
上面代码中,Promise.all方法接受一个数组做为参数,p一、p二、p3都是Promise实例,若是不是,就会先调用下面讲到的Promise.resolve方法,将参数转为 Promise 实例,再进一步处理(Promise.all方法的参数能够不是数组,但必须具备Iterator接口,且返回的每一个成员都是 Promise实例)
p的状态由p一、p二、p3决定,分红两种状况.
(1)只有p一、p二、p3的状态都变成fullfilled,p的状态才会变成fullfilled,此时p一、p二、p3的返回值组成一个数组,传递给p的回调函数.
(2)只要p一、p二、p3之中有一个被reject,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
- Promise.race
const p = Promise.race([p1, p2, p3]);
复制代码
上面代码中,只要p一、p二、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。
Promise.race方法的参数与Promise.all方法同样,若是不是Promise实例,就会先调用下面讲到的Promise.resolve方法,将参数转为 Promise实例,再进一步处理。
- Promise.resolve
将现有对象转为Promise对象,该实例的状态为resolved.
- Promise.reject
将现有对象转为Promise对象,该实例的状态为rejected.
- Promise.prototype.catch
catch方法其实就是.then(null, rejection)的别名
关于Promise的实现规范有兴趣的能够去看一下,接下来介绍一些现有的封装了Promise的库,在本文最后咱们也会一块儿来完成一个Promise库的实现:
1. Q库
能够经过npm install q安装使用
Q.fcall(function() {
return 100;
}).then(function(data) {
console.log(data);
})
复制代码
这里的fcall其实就相似于Promis.resolve方法
function read(url) {
return new Promise(function(resolve, reject) {
require('fs').readFile(url, 'utf8', function(err, data) {
if (err) reject(err);
resolve(data);
});
});
}
let Q = require('q');
Q.all([read('./2.promise/1.txt'), read('./2.promise/2.txt')]).then(function ([a1, a2]) {
console.log(a1, a2);
});
复制代码
一样它也有相似Promise.all这样的方法
let Q = require('q');
function read(url) {
let defer = Q.defer();
require('fs').readFile(url, 'utf8', function(err, data) {
if (err) defer.reject(err);
defer.resolve(data);
})
return defer.promise;
}
read('./2.promise/1.txt').then(function(data) {
console.log(data);
});
复制代码
一样也可使用defer这样一个语法糖(关于defer会在以后的Promise实现中解释)
2. Bluebird库
能够经过npm install bluebird安装
它有两个比较亮点的方法,promisify和promisifyAll
let fs = require('fs');
let bluebird = require('bluebird');
let read = bluebird.promisify(fs.readFile);
bluebird.promisifyAll(fs);
fs.readFileAsync('./2.promise/1.txt', 'utf8').then(function(data) {
console.log(data);
});
复制代码
所作的就是将传入的方法所有转成返回是Promise对象的新函数
背后实现原理其实也很简单
function promisify(fn) { // promise化 将回调函数在内部进行处理
return function (...args) {
return new Promise(function (resolve, reject) {
fn(...args, function (err, data) {
if (err) reject(err);
resolve(data);
})
})
}
}
function promisifyAll(obj) {
Object.keys(obj).forEach(key => { // es5将对象转化成数组的方法
if (typeof obj[key] === 'function') {
obj[key + 'Async'] = promisify(obj[key])
}
})
}
复制代码
关于generato的使用和原理这里就再也不赘述,你们能够去参考这里,这里就给个简单的例子:
// genrator函数要用* 来比标识,yield(暂停 产出)
// 它会将函数分割出好多个部分,调用一次next就会继续向下执行
// 返回结果是一个迭代器 迭代器有一个next方法
// yield后面跟着的是value的值
// yield等号前面的是咱们当前调用next传进来的值
// 第一次next传值是无效的
function* read() {
console.log(1);
let a = yield 'zf';
console.log(a);
let b = yield 9;
console.log(b);
return b;
}
let it = read();
console.log(it.next('213')); // {value:'zf',done:false}
console.log(it.next('100')); // {value:9,done:false}
console.log(it.next('200')); // {value:200,done:true}
console.log(it.next('200')); // {value:200,done:true}
复制代码
generator与Promise的搭配使用例子以下
let bluebird = require('bluebird');
let fs = require('fs');
let read = bluebird.promisify(fs.readFile);
function* r() {
let content1 = yield read('./2.promise/1.txt', 'utf8');
let content2 = yield read(content1, 'utf8');
return content2;
}
let it = r();
it.next().value.then(function(data) { // 2.txt
it.next(data).value.then(function(data) {
console.log(it.next(data).value);
});
})
复制代码
固然咱们能够看到调用时仍是得这样不停的嵌套去获取值,因此这里须要引入另外一个叫co的库,那么使用方法就能够变成以下:
let co = require('co');
let bluebird = require('bluebird');
let fs = require('fs');
let read = bluebird.promisify(fs.readFile);
function* r() {
let content1 = yield read('./2.promise/1.txt', 'utf8');
let content2 = yield read(content1, 'utf8');
return content2;
}
co(r()).then(function(data) {
console.log(data)
})
复制代码
co背后实现的原理其实也不复杂:
function co(it) {
return new Promise(function(resolve, reject) {
function next(d) {
let { value, done } = it.next(d);
if (!done) {
value.then(function (data) { // 2,txt
next(data)
}, reject)
} else {
resolve(value);
}
}
next();
});
}
复制代码
async+await就是目前为至,异步的最佳解决方案,它同时解决了
示例代码:
let bluebird = require('bluebird');
let fs = require('fs');
let read = bluebird.promisify(fs.readFile);
// 用async来修饰函数,aysnc须要配await, await只能接promise
// async和await(语法糖) === co + generator
async function r() {
try{
let content1 = await read('./2.promise/100.txt', 'utf8');
let content2 = await read(content1, 'utf8');
return content2;
} catch(e) { // 若是出错会catch
console.log('err', e)
}
}
// async函数返回的是promise
r().then(function(data) {
console.log('flag', data);
}, function(err) {
console.log(err);
})
复制代码