案例使用Promise解决按顺序请求a b c 三个json文件的回调地狱问题
一个 Promise 必然处于以下几种状态之一:
待定(pending): 初始状态,既没有被兑现,也没有被拒绝。
已兑现(fulfilled): 意味着操作成功完成。
已拒绝(rejected): 意味着操作失败。
一、Promise
1、对于异步操作的状态管理
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('wuyan');
// 对异步操作的执行结果进行判断
// if () {
// resolve()
// } else {
// reject();
// }
// resolve('成功')
// reject('失败')
}, 1000);
// then 里面第一个参数是必须的,第二个可以省略,也就是说可以只写已兑现
}).then(res => {
console.log(res);
}, err => {
console.log(err);
})
2、执行顺序(1 2 3)
// promise会立即执行,回调函数有异步性,属于微任务
let p2 = new Promise((resolve, reject) => {
console.log(1);
resolve();
})
console.log(2);
p2.then(res => {
console.log(3);
})
3、封装ajax
function ajax(url, successCallback, failCallback) {
//1、创建XMLHttpRequest对象
var xmlhttp;
//判断浏览器是否支持
if (window.XMLHttpRequest) {
xmlhttp = new XMLHttpRequest();
} else {
xmlhttp = new ActiveXObject('Microsoft.XMLHTTP');
}
// 2、发送请求
xmlhttp.open('GET', url, true);
xmlhttp.send();
// 3、服务器响应
xmlhttp.onreadystatechange = function () {
if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
var obj = JSON.parse(xmlhttp.responseText);
//console.log(obj);
successCallback && successCallback(obj);
} else if (xmlhttp.readyState === 4 && xmlhttp.status === 404) {
failCallback && failCallback(xmlhttp.statusText );
}
}
}
4、链式操作,不再是回调深渊
let p3 = new Promise((resolve, reject) => {
ajax('./static/a.json', res => {
console.log(res);
resolve('./static/b.json');
})
// 这里接收'./static/b.json'
}).then(res => {
console.log('a成功');
return new Promise((resolve, reject) => {
ajax(res, res => {
console.log(res);
resolve('./static/c.json');
})
})
// 这里接收./static/c.json,上面不返回新的Promise对像
// 这里顺序就会出错
}).then(res => {
console.log('B成功');
return new Promise((resolve, reject) => {
ajax(res, res => {
console.log(res);
resolve('c也成功了');
})
})
}).then(res => {
console.log(res);
})
5、 封装ajax请求
function getPromise(url) {
return new Promise((resolve, reject) => {
ajax(url, res => {
resolve(res);
},err => {
reject(err);
})
})
}
6、链式调用
getPromise('static/a.json')
.then(res => {
console.log(res);
return getPromise('static/b.json')
}).then(res => {
console.log(res);
return getPromise('static/c.json')
}).then(res => {
console.log(res);
})
7、一环出错会影响下一环
这里不存在aa.json这个文件,导致promise回调到err
getPromise('static/aa.json')
.then(res => {
console.log(res);
return getPromise('static/b.json')
}, err => {
console.log(err);
//上一个回调没有找到,返回reject()
// 所以需要再请求一下
return getPromise('static/b.json')
}).then(res => {
console.log(res);
return getPromise('static/c.json')
}).then(res => {
console.log(res);
})
8、catch 统一处理失败
getPromise('static/a.json')
.then(res => {
console.log(res);
return getPromise('static/b.json')
}).then(res => {
console.log(res);
return getPromise('static/c.json')
}).then(res => {
console.log(res);
}).catch(err => {
console.log(err);
})
二、Promise 静态方法
1、Promise.resolve()
方法返回一个以给定值解析后的Promise 对象。如果这个值是一个 promise ,那么将返回这个 promise;如果这个值是thenable(即带有"then"方法),返回的promise会“跟随”这个thenable的对象,采用它的最终状态;否则返回的promise将以此值完成。此函数将类promise对象的多层嵌套展平。
let p1 = Promise.resolve('success');
// console.log(p1);//Promise {<fulfilled>: "success"}
// 那么既然能返回成功,在then里面能不能打印呢
p1.then(res => {
console.log(res);//success
})
2、Promise.reject()
方法返回一个带有拒绝原因的Promise对象。
let p2 = Promise.reject('fail');
//console.log(p2);//Promise {<rejected>: "fail"}
p2.catch(err => {
console.log(err);
})
3、应用
function foo(flag) {
if(flag) {
return new Promise(resolve => {
resolve('success');
})
} else {
// return 'fail';
return Promise.resolve('fail');
}
}
foo(false).then(res => {
console.log(res);//fail
}, err => {
console.log(err);
})
4、Promise.all()
方法接收一个promise的iterable类型(注:Array,Map,Set都属于ES6的iterable类型)的输入,并且只返回一个Promise实例,
那个输入的所有promise的resolve回调的结果是一个数组。这个Promise的resolve回调执行是在所有输入的promise的resolve回调都结束,或者输入的iterable里没有promise了的时候。它的reject回调执行是,只要任何一个输入的promise的reject回调执行或者输入不合法的promise就会立即抛出错误,并且reject的是第一个抛出的错误信息。
简单说:all()这个静态方法是等多个任务执行完之后再执行,只要有一个任务失败,那么all()就会认为整个都是失败的
let p3 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log(1);
resolve('1成功');
}, 1000);
})
let p4 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log(2);
resolve('2成功');
// reject('2失败');
}, 2000);
})
let p5 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log(3);
resolve('3成功');
}, 3000);
})
Promise.all([p3, p4, p5]).then(res => {
console.log(res);
},err => {
console.log(err);
})
5、Promise.race()
方法返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。
简单说:race()这个静态方法是 等多个任务执行完之后再执行,只要有一个任务成功,那么all()就会认为整个都是成功的
Promise.race([p3, p4, p5]).then(res => {
console.log(res);
},err => {
console.log(err);
})
6、应用
const imgArr = ['1.jpg', '2.jpg', '3.jpg'];
let promisrArr = [];
imgArr.forEach(item => {
promisrArr.push(new Promise((resolve, reject) => {
//上传图片操作
resolve();
}))
});
Promise.all(promisrArr).then(res => {
console.log('图片全部上传完成!');
})
// 7、图片加载成功或超时
function getImg() {
return new Promise((resolve, reject) => {
let img = new Image();
img.onload = function () {
resolve(img);
}
img.src = 'http://120.55.162.187:8080/%E4%B8%BB%E9%A1%B5/%E5%A3%81%E7%BA%B88.jpg';
})
}
function timeout() {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject('图片请求超时');
}, 1000);
})
}
Promise.race([getImg(), timeout()]).then(res => {
console.log(res);
}).catch(err => {
console.log(err);
})
三、Generator
生成器函数在执行时能暂停,后面又能从暂停处继续执行。
function* foo() {
for (let i = 0; i < 4; i++) {
let arr = ['我', '是', '吴', '炎'];
console.log(arr[i]);
//位置很关键
yield i;
}
}
let f = foo();
// 循环完成后,状态为true
console.log(f.next());//我 {value: 0, done: false}
console.log(f.next());//是 {value: 1, done: false}
console.log(f.next());//吴 {value: 2, done: false}
console.log(f.next());//炎 {value: 3, done: false}
console.log(f.next());//{value: undfined, done: true}
调用一个生成器函数并不会马上执行它里面的语句,而是返回一个这个生成器的 迭代器 ( iterator )对象。当这个迭代器的 next()方法被首次(后续)调用时,其内的语句会执行到第一个(后续)出现yield的位置为止,yield 后紧跟迭代器要返回的值。或者如果用的是yield*(多了个星号),则表示将执行权移交给另一个生成器函数(当前生成器暂停执行)。
next()方法返回一个对象,这个对象包含两个属性:value 和 done,value 属性表示本次 yield 表达式的返回值,done属性为布尔类型,表示生成器后续是否还有 yield 语句,即生成器函数是否已经执行完毕并返回。
yield注意事项
不能作为构造函数使用 yield 只能在生成器内部使用(内部嵌套在别的函数都不行)
function* gen(args) {
args.forEach(element => {
yield element + 1;
});
}
调用 next()方法时,如果传入了参数,那么这个参数会传给上一条执行的 yield语句左边的变量
function* gen1(x) {
let y = 2 * (yield(x + 1));
let z = yield(y / 3);
return x + y + z;
}
let g = gen1(5);
//next()里面的是上一次yield的结果
console.log(g.next());//(x + 1) = 6
console.log(g.next(12));//y = 2 * 12 = 24
console.log(g.next(13));// z = 13 y = 24 x = 5 => 42
1、死循环?
function* count(x = 1) {
while (true) {
if (x % 7 === 0) {
yield x;
}
x++;
}
}
let n = count();
console.log(n.next().value);//7
console.log(n.next().value);//14
console.log(n.next().value);//21
2、Generator对异步操作进行管理
封装好的ajax,先手动开启,后面由回调函数自动请求
function ajax(url, callback) {
//1、创建XMLHttpRequest对象
var xmlhttp;
//判断浏览器是否支持
if (window.XMLHttpRequest) {
xmlhttp = new XMLHttpRequest();
} else {//兼容早期浏览器
xmlhttp = new ActiveXObject('Microsoft.XMLHTTP');
}
// 2、发送请求
xmlhttp.open('GET', url, true);
xmlhttp.send();
// 3、服务器响应
xmlhttp.onreadystatechange = function () {
if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
var obj = JSON.parse(xmlhttp.responseText);
//console.log(obj);
callback(obj);
}
}
}
function request(url) {
ajax(url, res => {
//每次回调都会调用next(),除了第一次是手动调用外
getDate.next(res);
})
}
function* gen() {
let res1 = yield request('./static/a.json');
console.log(res1);//a
let res2 = yield request('./static/b.json');
console.log(res2);//b
let res3 = yield request('./static/c.json');
console.log(res3);//c
}
let getDate = gen();
getDate.next();//