异步的产生和背景
最早的ajax异步回调,在一个异步请求中写入一个callback函数可以实现早期易用的异步调用,同时也陷入了许多的弊端之中,多层嵌套不能一个中断或者报错。、 万能的coder大神们 衍生出了promise。解决了callback hell的困惑和问题。同时改进发展到generator+co更好的方案。如今在非刚需node环境下也有了一套不需要安装多余包即可使用的async+await。
源码解读
//正常promise用法
let promise = New Promise(function(resolve,rejevt){
//这里是要执行的函数 任何一个函数都有返回正确和错误的流程,这里也一样resolve代表正常返回,reject表示错误返回
})
promise.then(function(res){
//正常正确的逻辑
},function(err)}{
//返回异常的信息和结果
})
>>>>>>>>>>>>>>>>>>>>>>>=====分割线 下面开始解读
//promise.js 规范文本 按照https://promisesaplus.com/规范来解读
function Promise(executor){ //executor是一个执行函数,在实例化Promise的时候就开始执行 == 上面Promise的执行参数
let self = this;
self.status = 'pending';//任何http请求在没有收到返回信息的时候 状态都是pengding,这里也用来记录改变的状态
self.value = undefined;//定义成功的数据
self.reason = undefined;//定义失败的原因
//如promise用法,这里需要传入2个参数
//网络请求不可能一直请求,总有一个状态来表示 根据A+规范2.1 Promise States 在没有结果之前 状态值都是可以更改的
function resolve(value){
//执行正常的操作 传入成功返回值,并修改状态resolved 防止多次调用修改状态值
if(self.status === 'pending'){
self.value = value;
self.status = 'resolved';
}
}
function reject(reason){
//执行异常的处理 传入异常原因,并修改状态为rejected 防止多次调用修改状态值
if(self.status === 'pending'){
self.value = reason;
self.status = 'rejected';
}
}
executor(resolve,reject)
}
//在实例化Promise 之后 可以链式调用then方法。因此then是在Promise的原型连上 根据A+规范2.2 The then Method
Promise.then = function(onFulfilled, onRejected){
//这里使用 第一个函数为 成功状态下的回调, 第二个函数为失败状态下的回调。怎么和成功失败想联系,需要记得构造函数中的状态值 status resolved/成功 rejected/失败
let self = this;
if(self.status === 'resolved'){
onFulfilled(self.value)
}
if(self.status === 'rejected'){
onRejected(self.reason)
}
}
//最终不要忘记暴露这个模块 如果需要使用import 请看
module.exports = Promise
复制代码
目前这里完全是同步执行的代码
当然在实际应用过程中都是异步的操作,不可能在实例化的时候就直接有结果传给then吧,下面我们来改造一下函数。
//定义2个数组,来存放回调事件,promise.then(suc1,err1).then(suc2,err2),每一次then的回调都不同
//在创建promise函数的地方,给每个成功和失败时间存到函数里面。
function Promise(executor) { //executor是一个执行函数,同步执行
.....
self.reason = undefined; //默认失败的原因
self.onResolvedCallBacks = [];//成功的回调数组;
self.onRejectedCallBacks = [];//失败的回调数组;
}
Promise.prototype.then = function(resolve,reject){
......
if(self.status === 'pending'){
//在此处以队列的形式存放所有的事件
self.onResolvedCallBacks.push(resolve);
self.onRejectedCallBacks.push(reject);
}
}
//如何在成功后的回调里面执行这些函数,回到状态改变的函数执行时候
function Promise(executor) { //executor是一个执行函数,同步执行
...
if(self.status === 'resolved'){
self.status = 'resolved';
self.value = value;
//在这里把所有成功的函数都通过循环执行一边,全部是顺序同步执行
self.onResolvedCallBacks.forEach(function(fn){
fn();
})
}
if(self.status === 'rejected'){
self.status = 'rejected';
self.reason = reason;
//在这里把所有失败的函数都通过循环执行一边,全部是顺序同步执行
self.onRejectedCallBacks.forEach(function(fn){
fn();
})
}
}
//至此就完成了 异步的操作, 有没有想起类似的一种设计模式,的确很像
//同时再来解释一下promise的链式调用。以及传空值的值穿透操作。
promise.then(cb,cb).then(cb.cb),//这里有一个误区,并不类似于jq的链式操作返回this
let promise = new Promise(function(resolve,reject){
resolve('suc');
});
//根据上面直接执行了 resolve成功的函数,then会触发 第一个成功的函数,,返回data,
let p1 = promise.then(function(data){
console.log(data)
},function(err){
console.log(err)
}
//但是 当 如上promise第一个函数返回的是 let p2 = promise.then(function(data){
throw new Error('error')
},function(err){
console.log(err)
}) //此时,p1.then 和p2.then 已经分别指向了成功和失败,因此这一点的this指向已经不同。所以这一点不能通过this来链式调用,靠的其实就是promise本身来链式调用的,在每次resolved,rejecred,pending其实都已经重新实例化了一个promise。因此可以一直来then。可参考下列异常2理解返回的promise
promise.then().then(),详解
//只是多做一层判断。如果曾在则使用自身所带的,不存在原值reutrn。。是不是很简单
Promise.prototype.then = function (onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function'?onFulfilled:function(res){
return res;
}
onRejected = typeof onRejected === 'function'?onRejected:function(err){
throw err
}
...
}
复制代码
小结:对于每个内部构造中的onFulfilled和onRejected按照promise的规定必须包裹一层setTimeout。具体原因需要参考事件环的执行顺序。不多累述。简单了解可看下面代码以及注释。
业务完成并不能代表这个代码符合大多数人的需求,还需要许多的磨合已经使用才能成为一个优秀的库。对此promise对于错误捕获信息的处理也是做到了极致。不得不服大佬对代码细腻程度的把控相当严谨。 以下对于有更高追求的码哥可以深入了解一下。
异常1: promise (function(resolve,reject){ throw new Error('error') }); 是不是直接傻眼了,对此需要在executor这里做一层try {executor(resolve, reject)} catch(e){ reject(e)} 捕获异常错误后直接返回错误信息。
异常2:then之后继续return promise promise.then(function(data){ return new Promise(function(resolve,reject){ ... }) },function(err){ return new Promise(function(resolve,reject){ ... })
})
//不难理解,这里每次都会重新定义一个新的promise来进行保存当前操作已经下一步的操作。。setTimeout暂时可以不理解,需要深入理解请参考promisezAplus中的规范标准。
Promise.prototype.then = function (onFulfilled, onRejected) { ... let promise2;//链式调用定义的新promise //成功和失败都可能会返回一个新的promise if (self.status === 'resolved') { promise2 = new Promise(function(resolve,reject){ setTimeout(function(){ onFulfilled(self.value);} ) })
}
if (self.status === 'rejected') {
promise2 = new Promise(function(resolve,reject){
setTimeout(function(){
onRejected(self.reason);
})
})
}
if (self.status === 'pending') {
promise2 = new Promise(function(resolve,reject){
self.onResolvedCallBacks.push(function () {
onFulfilled(self.value);
})
self.onRejectedCallBacks.push(function () {
onRejected(self.value);
})
})
}
return promise2;
复制代码
}
//懵逼异常3:返回自身promise以及无限嵌套promise,并且与他们promise库兼容
function resolvePromise(p2,lastResult,resolve,reject){
//lastResult,有可能是别人返回的promise或者是一个单纯的返回值
//p2 和 lastResult 如果相同,这里应该是不合法的
if(p2 === lastResult){
//这里应该报一个错误类型
return reject(new TypeError('循环引用'));
}
//判断lastResult 是不是一个promise,promise应该是一个对象.
let called; // 表示是否调用过成功或者失败
if(lastResult!=null && (typeof lastResult === 'object' || typeof lastResult === 'function')){
//可能是个promise 也可能是个{}, 是不是个promise 可以用对象中是否有then方法,如果有then就认为是一个promise
try { //else 的可能性{then:1}
let then = lastResult.then;
//其他的所有形况认为是promise
if(typeof then === 'function'){
//成功
then.call(lastResult,function(y){
if(called)return
called = true;
//y可能还是一个promise,再去解析 直到返回的是一个普通值
resolvePromise(p2,y,resolve,reject)
},
//失败
function(err){
if(called)return
called = true;
reject(err);
});
}else{
resolve(lastResult);
}
} catch (e) {
if (called) return
called = true;
reject(e);
}
}else{
//返回一个普通值 则直接返回成功态
resolve(lastResult);//表示成功了
}
复制代码
}
测试 promise是否符合规范。
npm install -g promises-aplus-tests
安装成功后
找到你promise的文件目录打开命令窗口
promises-aplus-tests ./Promise.js
查看是否测试通过。
扩展一些内容
高阶函数
你有多少函数是链式调用的,你又有多少函数可以作为参数或者函数作为返回值
判断数据类型 isType
//正常的简单的判断函数吧
function isType(type,content){
return Object.prototype.toString.call(content) === `[object ${type}`
}
//高级一点的
//这里可以批量生成函数
function isType(type){ //偏函数
return function(content){
return Object.prototype.toString.call(content) === `[object ${type}`
}
}
let isString = isType('String');
let isArray = isType('Array');
console.log(isArray('hello'));//false
// 2 预置函数 (在执行函数多少次后执行回调) 在确定外界大的状态或者条件后去执行回调
function after(times,callBack){
return function(){
if(--times === 0 ){
callBack();
}
}
}
let eat = after(3,function(){
![](https://user-gold-cdn.xitu.io/2018/4/8/162a1942427a8084?w=3200&h=2100&f=jpeg&s=1664014)
console.log('不吃了');
})
eat();//没打印
eat();//没打印
eat();//打印 '不吃了';
这里与promise的执行算是一点同步的见解。
大致是这样的流程
异步读取文件,方法大同小异与promise.all
let fs = require('fs');
let a = [];
function outPut(length,callBack){
return function(data){
a.push(data);
if(--length == 0 ){
callBack(arr);
}
}
}
let out = after(3,function(arr){
console.log(arr);
})
fs.readFile('文件1路径','utf8',function(err,res){
outPut(res);//存取其中一条数据
})
fs.readFile('文件2路径','utf8',function(err,res){
outPut(res);//存取另一条数据
})
复制代码