关于异步
所谓”异步”,简单说就是一个任务不是连续完成的,可以理解成该任务被人为分成两段,先执行第一段,然后转而执行其他任务,等做好了准备,再回过头执行第二段。
比如,有一个任务是读取文件进行处理,任务的第一段是向操作系统发出请求,要求读取文件。然后,程序执行其他任务,等到操作系统返回文件,再接着执行任务的第二段(处理文件)。这种不连续的执行,就叫做异步。
相应地,连续的执行就叫做同步。由于是连续执行,不能插入其他任务,所以操作系统从硬盘读取文件的这段时间,程序只能干等着。
简单的说同步就是大家排队工作,异步就是大家同时工作。如果你还不太明白异步与同步,多看看JS基础的文章。
异步的发展历史
1.CallBack写法
CallBack意为“回调函数”,即异步操作执行完后触发执行的函数,例如:
$.get("http://api.xxxx.com/xxx",callback);
当请求完成时就会触发callback函数。
callback可以完成异步操作,但是经历过JQuery时代的人应该都对某一种需求折磨过,举个例子:项目要求前端ajax请求后端接口列表类型名称,然后在用类型名称ajax请求列表id,在用id请求列表具体内容,最后代码大概是这样的
$.ajax({
url: "type",
data:1,
success: function (a) {
$.ajax({
url: "list",
data:a,
success: function (b) {
$.ajax({
url: "content",
data:b,
success: function (c) {
console.log(c)
}
})
}
})
}
})
这是是单纯的嵌套代码,如若再加上业务代码,代码可读性可想而知,如果是开发起来还好,但是后期的维护和修改的难度足以让人疯掉。这就是那个JQuery时代的“回调地狱”问题。
2.Promise
为了解决“回调地狱”问题,提出了Promise对象,并且后来加入了ES6标准,Promise对象简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。
resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。
promise.then(function(value) {
// success
}, function(error) {
// failure
});
then方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为resolved时调用,第二个回调函数是Promise对象的状态变为rejected时调用。其中,第二个函数是可选的,不一定要提供。这两个函数都接受Promise对象传出的值作为参数。
这样采用 Promise,解决“回调地狱”问题,比如连续读取多个文件,写法如下。
var readFile = require('fs-readfile-promise');
readFile(fileA)
.then(function (data) {
console.log(data.toString());
})
.then(function () {
return readFile(fileB);
})
.then(function (data) {
console.log(data.toString());
})
.catch(function (err) {
console.log(err);
});
可见这种写法要比CallBack写法直观的多。但是,有没有更好的写法呢?
3.Generator 函数
Genrator 函数要用* 来比标识,yield关键字表示暂停。将函数分割出好多个部分,调用一次next就会继续向下执行。返回结果是一个迭代器,迭代器有一个next方法。
function* read() {
console.log(1);
let a = yield '123';
console.log(a);
let b = yield 9
console.log(b);
return b;
}
let it = read();
console.log(it.next('213')); // {value:'123',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}
yield后面跟着的是value的值,yield等号前面的是我们当前调用next传进来的值,并且第一次next传值是无效的。
处理异步的时候Generator和Promise搭配使用
let bluebird = require('bluebird');
let fs = require('fs');
let read = bluebird.promisify(fs.readFile);//将readFile转为Promise对象的实例
function* r() {
let content1 = yield read('./2.promise/1.txt', 'utf8');
let content2 = yield read(content1, 'utf8');
return content2;
}
这样看起来是我们想要的样子,但是只写成这样还不行,想得到r()的结果还要对函数进行处理
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();
});
}
co(r()).then(function (data) {
console.log(data)//得到r()的执行结果
})
这样的处理方式显然很麻烦,并不是我们想要,我们想要直观的写起来就就像同步函数,而且简便的方式处理异步。有这样的方法吗?
4.async-await函数
ES2017 标准引入了 async 函数,使得异步操作变得更加方便。
async 函数是什么?一句话,它就是 Generator 函数的语法糖。
let bluebird = require('bluebird');
let fs = require('fs');
let read = bluebird.promisify(fs.readFile);
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函数就是将 Generator 函数的星号(*)替换成async,将yield替换成await,仅此而已。
async函数返回的是promise
r().then(function(data){
console.log(data);
},function(err){
})
至此,async-await函数已经可以我们满意,以后会不会出现更优秀的方案?以我们广大程序群体的创造力,相信一定会有的。
Promise原理分析
async-await函数其实只是Generator函数的语法糖,而Generator函数的实现方式也是要基于Promise,所以我们队Promise的实现原理进行分析。
Promise对象有以下几种状态:
- pending: 初始状态, 既不是 fulfilled 也不是 rejected.
- fulfilled: 成功的操作.
- rejected: 失败的操作.
在上面了解了Promise的基本用法后,我们先将Promise的框架搭起来
function Promise(executor) { // executor是一个执行函数
let self = this;
self.status = 'pending';
self.value = undefined; // 默认成功的值
self.reason = undefined; // 默认失败的原因
self.onResolvedCallbacks = []; // 存放then成功的回调
self.onRejectedCallbacks = []; // 存放then失败的回调
function resolve(value) { // 成功状态
}
function reject(reason) { // 失败状态
}
try {
executor(resolve, reject)
} catch (e) { // 捕获的时候发生异常,就直接失败了
reject(e);
}
}
Promise.prototype.then = function (onFulfilled, onRjected) {
//then方法
})
接下来当调用成功状态resolve的时候,会改变状态,执行回调函数:
function resolve(value) { // 成功状态
if (self.status === 'pending') {
self.status = 'resolved';
self.value = value;
self.onResolvedCallbacks.forEach(function (fn) {
fn();
});
}
}
reject函数同理
function reject(reason) { // 失败状态
if (self.status === 'pending') {
self.status = 'rejected';
self.reason = reason;
self.onRejectedCallbacks.forEach(function (fn) {
fn();
})
}
}
接下来我们完成then函数
Promise.prototype.then = function (onFulfilled, onRjected) {
let self = this;
let promise2; //返回的promise
if (self.status === 'resolved') {
promise2 = new Promise(function (resolve, reject) {
})
}
if (self.status === 'rejected') {
promise2 = new Promise(function (resolve, reject) {
})
}
// 当调用then时可能没成功 也没失败
if (self.status === 'pending') {
promise2 = new Promise(function (resolve, reject) {
})
}
return promise2;
}
Promise允许链式调用,所以要返回一个新的Promise对象promise2
Promise.prototype.then = function (onFulfilled, onRjected) {
//成功和失败默认不穿给一个函数
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : function (value) {
return value;
}
onRjected = typeof onRjected === 'function' ? onRjected : function (err) {
throw err;
}
let self = this;
let promise2; //返回的promise
if (self.status === 'resolved') {
promise2 = new Promise(function (resolve, reject) {
setTimeout(function () {
try {
let x = onFulfilled(self.value);
// x可能是别人promise,写一个方法统一处理
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
})
})
}
if (self.status === 'rejected') {
promise2 = new Promise(function (resolve, reject) {
setTimeout(function () {
try {
let x = onRjected(self.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
})
})
}
// 当调用then时可能没成功 也没失败
if (self.status === 'pending') {
promise2 = new Promise(function (resolve, reject) {
// 此时没有resolve 也没有reject
self.onResolvedCallbacks.push(function () {
setTimeout(function () {
try {
let x = onFulfilled(self.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e)
}
})
});
self.onRejectedCallbacks.push(function () {
setTimeout(function () {
try {
let x = onRjected(self.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
})
});
})
}
return promise2;
}
在promise2内部定义一个变量x为回调函数的返回值,由于返回值可能会有多种可能的情况,所以我们定义一个resolvePromise函数统一处理
返回值可以分为
- promise返回自己 (报错循环引用)
- 返回promise对象 (根据promise对象调用成功或失败回调函数)
- 返回普通值 (调用成功回调函数传入返回值)
- 报错 (调用失败回到传入错误)
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(new TypeError('循环引用了'))
}
// 判定x是不是一个promise,promise应该是一个对象
let called; // 表示是否调用过成功或者失败
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
try { // {then:1}
let then = x.then;
if (typeof then === 'function') {
// 成功
then.call(x, function (y) {
if (called) return
called = true
// y可能还是一个promise,在去解析直到返回的是一个普通值
resolvePromise(promise2, y, resolve, reject)
}, function (err) { //失败
if (called) return
called = true
reject(err);
})
} else {
resolve(x)
}
} catch (e) {
if (called) return
called = true;
reject(e);
}
} else { // 说明是一个普通值
resolve(x); // 调用成功回调
}
}
如果返回值为对象或函数,且有then方法,那我们就认为是一个promise对象,去调用这个promise进行递归,直到返回普通值调用成功回调。
最后,再加上一个catch方法,很简单
Promise.prototype.catch = function (callback) {
return this.then(null, callback)
}
这些就是promise的主要功能的原理,附上完整代码
function Promise(executor) { // executor是一个执行函数
let self = this;
self.status = 'pending';
self.value = undefined; // 默认成功的值
self.reason = undefined; // 默认失败的原因
self.onResolvedCallbacks = []; // 存放then成功的回调
self.onRejectedCallbacks = []; // 存放then失败的回调
function resolve(value) { // 成功状态
if (self.status === 'pending') {
self.status = 'resolved';
self.value = value;
self.onResolvedCallbacks.forEach(function (fn) {
fn();
});
}
}
function reject(reason) { // 失败状态
if (self.status === 'pending') {
self.status = 'rejected';
self.reason = reason;
self.onRejectedCallbacks.forEach(function (fn) {
fn();
})
}
}
try {
executor(resolve, reject)
} catch (e) {
reject(e);
}
}
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(new TypeError('循环引用了'))
}
let called;
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
try {
let then = x.then;
if (typeof then === 'function') {
then.call(x, function (y) {
if (called) return
called = true
resolvePromise(promise2, y, resolve, reject)
}, function (err) { //失败
if (called) return
called = true
reject(err);
})
} else {
resolve(x)
}
} catch (e) {
if (called) return
called = true;
reject(e);
}
} else {
resolve(x);
}
}
Promise.prototype.then = function (onFulfilled, onRjected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : function (value) {
return value;
}
onRjected = typeof onRjected === 'function' ? onRjected : function (err) {
throw err;
}
let self = this;
let promise2;
if (self.status === 'resolved') {
promise2 = new Promise(function (resolve, reject) {
setTimeout(function () {
try {
let x = onFulfilled(self.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
})
})
}
if (self.status === 'rejected') {
promise2 = new Promise(function (resolve, reject) {
setTimeout(function () {
try {
let x = onRjected(self.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
})
})
}
if (self.status === 'pending') {
promise2 = new Promise(function (resolve, reject) {
self.onResolvedCallbacks.push(function () {
setTimeout(function () {
try {
let x = onFulfilled(self.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e)
}
})
});
self.onRejectedCallbacks.push(function () {
setTimeout(function () {
try {
let x = onRjected(self.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
})
});
})
}
return promise2;
}
Promise.prototype.catch = function (callback) {
return this.then(null, callback)
}