今天带大家了解一个神器,promise, promise所解决的问题有哪些?
- 解决回调地狱(多个ajax嵌套);
- 解决并发异步(同一时刻内获取并发的结果);
- 链式调用。
1. 什么是promise
promise 分为3种状态,分别是:
- pendding:初始状态,既不是成功,也不是失败状态。
- fulfilled: 意味着操作成功完成。
- rejected: 意味着操作失败。
2.promise的使用
promise是一个类,内置的类,使用方式是new Promise(),里面有一个参数executor,executor有两个参数resolve,rejected,promise必须有方法then,then有两个参数onFulfilled, onRejected。
2.1 参数
let p = new Promise((resolve,reject)=>{
//有一个参数叫executor,executor有两个参数resolve,对应的状态fulfilled操作成功完成,rejected操作失败
});
p.then(onFulfilled, onRejected){
//有两个参数onFulfilled, onRejected
}
复制代码
2.2 promise的基本使用方法
2.2.1情况1:
let p = new Promise((resolve, reject)=> {
resolve('林俊杰');
});
p.then(data=>{
console.log(data);//'林俊杰'
},err=>{
console.log(err);
})
复制代码
- 上面例子走的是成功时的内容,而输出的data是resolve的参数
2.2.2情况2:
let p = new Promise((resolve, reject)=> {
reject('周杰伦');
resolve('林俊杰');
});
p.then(data=>{
console.log(data);
},err=>{
console.log(err);//'周杰伦'
})
复制代码
- 上面例子走到失败的内容,同样输出的是reject的参数,不同的是promise中同时存在reject();resolve();时,走的是放在前面的一个状态,只要走了失败就不会走成功(只要走了成功就不会走失败)
2.2.3情况3
let p = new Promise((resolve, reject)=> {
throw new Error('林俊杰');
});
p.then(data=>{
console.log('d',data);
},err=>{
console.log('e',err);//e Error: 林俊杰
})
复制代码
- 上面例子,抛出错误时,同样走到了reject,所以当抛错时也会走到reject,并不会报错。。。(模拟时可以使用try...catch)
接下来是模拟手写的promise(根据promise a+)
class MyPromise {
constructor(executor) {//参数executor放在constructor中
// 默认状态是等待态
this.status = 'pending';
this.value = undefined;//成功的参数
this.reason = undefined;//失败的参数
let resolve = (data) => {//注意this,最好用es6箭头函数
if (this.status === 'pending') {//只有当状态是pending时才会执行,下同
this.value = data;
this.status = 'fulfilled';
}
}
let reject = (reason) => {
if (this.status === 'pending') {
this.reason = reason;
this.status = 'rejected';
}
}
try { // 执行时可能会发生异常eg:throw new Error();
executor(resolve, reject);//executor是promise参数,executor有两个自己的参数
} catch (e) {
reject(e); // promise失败了
}
}
then(onFulFilled, onRejected) {
if (this.status === 'fulfilled') {
onFulFilled(this.value);
}
if (this.status === 'rejected') {
onRejected(this.reason);
}
}
}
module.exports = Promise;
复制代码
- 上面是最简单的一个实现,那么问题来了,如果,在我的promise中有一个异步的,比如定时器
let p = new Promise((resolve, reject)=> {
setTimeout(()=>{
reject('林俊杰');
},1000)
});
p.then(data=>{
console.log('d',data);
},err=>{
console.log('e',err);//e : 林俊杰
})
复制代码
- 内置的promise会等到定时器1s执行完以后再执行 then,而此时我们自己完成的MyPromise是不会输出任何,原因在于,then要先于异步的定时器执行,而此时的then的状态是pending,所以不会走then的任何状态。所以我们需要把then的成功的函数与失败的函数分别用数组存储起来。onResolvedCallbacks=[],onRejectedCallbacks=[],放在实例上,所以当状态是pending时,存起来
if(this.status === "pending") {
//相当于发布订阅
this.onResolvedCallbacks.push(()=>{
onFulFilled(this.value);
})
this.onResolvedCallbacks.push(()=>{
onRejected(this.reason);
})
}
复制代码
- 相当于发布订阅模式,只有以下状态改变的时候调用
let resolve = (data)=>{
if(this.status === 'pending') {
this.value = data;
this.status = 'fulfilled';
this.onResolvedCallbacks.forEach(fn=>fn());
}
}
let reject = (err)=>{
if(this.status === 'pending') {
this.reason = err;
this.status = 'rejected';
this.onRejectedCallbacks.forEach(fn=>fn());
}
}
复制代码
- 以上是基本的实现,那最主要的还是promise的链式调用。
2.3 promise的链式调用方法
let p = new Promise((resolve, reject) => {
resolve();
});
p.then((data) => {
// return new Promise((resolve,reject)=>{
// resolve(new Promise((resolve,reject)=>{
// reject('1234565');
// }))
// })//返回值是promise,会取promise的返回结果作为外层下一次then的参数
return 123;//返回值是普通值,直接把值作为外层下一次then的参数
return new Error;//返回的是普通值
}, err => {
console.log('p1:err', err);
}).then((data)=> {
console.log('d',data);
},err=>{
console.log('e',err);
})
复制代码
- then的返回值如果是promise,会取promise的返回结果作为外层下一次then的参数,如果返回的是普通值,直接把值作为外层下一次then的参数
- 那我们接着往下写吧我们看到的是
let p = new Promise((resolve, reject) => {
resolve('林俊杰');
});
p.then().then()
p.then();
复制代码
- 上面例子的两个then方法是不一样的结果,所以then方法调用后,返回的是新的promise(也有可能不是promise),链式写法靠的是返回新的promise(而不是this),有可能是失败走向成功的,所以then里面要return新的promise2
- 此时主要处理promise2(简称p2),与其返回值X的关系,方法resolvePromise(promise2, x, resolve, reject);
2.3.1 p2的返回值x也是p2,如下:
let promise2 = p.then(()=>{
return promise2;//Chaining cycle detected for promise,会报错循环引用。
})
复制代码
- 上面例子中,p2会等着自己的返回结果,一直没有结果不会调成功也不会调用失败,会报循环引用错误
2.3.2 p2的返回值x有可能是普通值,如下:
let promise2 = p.then((data)=>{
return '林俊杰';
},(err)=>{
return 'z'
})
promise2.then((data) => {
console.log('d', data); //d 林俊杰
}, (err) => {
console.log('e', err)
});
复制代码
- 上面例子,p2的返回值如果是普通值(只要不抛出错误throw new Error()),不管执行的是成功函数(onFulfilled)还是失败函数(onRejected),都会走到下一个then的成功态里
2.3.3 p2的返回值x有可能是函数,那么认为它是promise,如下:
let p2 =p.then((data) => {
return new Promise((resolve, reject)=>{
resolve(new Promise((resolve, reject)=>{
resolve(1111)
}))
})
}, (err) => {}).then((data) => {
console.log('d', data);//d 1111
}, err => {
console.log('e', err);
})
复制代码
- 上面例子,p2的返回值是多层promise嵌套,需要递归调用resolvePromise方法。
- 接下来根据promise a+来写方法resolvePromise
function resolvePromise(promise2, x, resolve, reject) {
//promise2返回新的promise,x为promise2的返回值,另外两个是参数
// 判断返回值是不是promise2 本身
if (promise2 === x) {//报错,见2.3.1
return reject(new TypeError('Chaining cycle detected for promise'));
}
// x不是null或者是对象或者函数(当然null的话是返回null,同普通值)
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
let called; // 就是为了防止,调取了成功态,在去调用失败态,
//或者调取了失败态,在去调取成功态,弄一个标识
try { // 防止取then时出现异常 例如Object.defineProperty,修改了then方法
let then = x.then; // 取x的then方法 {then:{}}
if (typeof then === 'function') { // 如果then是函数我就认为它是promise
// call:this指向x,后面分别是成功的回调和失败的回调
then.call(x, y => { // 如果y是promise就继续递归解析promise
if(called) return;
called = true;
resolvePromise(promise2,y,resolve,reject);
}, r => { // 只要失败了就失败了
if (called) return;
called = true;
reject(r);
});
}else{ // then是一个普通对象,就直接成功即可
resolve(x);
}
} catch (e) {
if (called) return;
called = true;
reject(e);
}
} else {
resolve(x); // x就是一个普通值,例子见2.3.2
}
}
复制代码
2.4 promise值的穿透
let p = new Promise((resolve, reject) => {
resolve('林俊杰');
});
p.then().then().then((data)=>{
console.log(data);//林俊杰
})
复制代码
- 上面例子,前面两个then都没有传参数或者参数为null,但是仍然将数据传给了最后一个then的onFulfilled成功态或者onRejected失败态,所以在then方法中首先要判断一下参数
// 解决onFulFilled,onRejected没有传的问题
onFulFilled = typeof onFulFilled === 'function' ? onFulFilled : y => y;
onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err; };
复制代码
对于原生的promise的then方法来说是属于异步的,会先执行同步代码以后再去执行then,例如:
p.then(()=>{console.log(1});
console.log(2);
复制代码
- 上面例子中可以得到的是2比1要先输出,所以then是异步,所以每一个执行都加一个定时器包起来
2.5 promise的其他方法
2.5.1 catch
p.then().then().catch((err)=>{
console.log(err)
}).then((data)=>{
console.log(data)
})
复制代码
其实catch是then的一个简写的方法,没有传成功的回调的时候走的是catch,但是catch走完了,还会继续往下走then,所以返回的依旧是promise。
catch(rejected){
return this.then(null, rejected)
}
复制代码
2.5.2 resolve + reject
Promise.resolve = function(val) {
return new Promise((resolve, reject)=> resolve(val))
}
Promise.reject = function(val){
return new Promise((resolve, reject)=> reject(val))
}
复制代码
2.5.3 all
原生上的all方法,以数组的形式把两个promise
p.all([promise1,promise2]).then((arr)=>{
consolr.log(arr)
})
复制代码
- 其中arr是all里面promise执行完的内容,顺序依次,全成功才成功,一个失败就失败
Promise.all = function(promises) {
return new Promise((resolve,reject)=> {
let arr = [];
let i = 0;
function processData(index, data) {
arr[index] = data;
i++;
if(i === promises.length) {
resolve(arr);
}
}
for(let i =0;i<promises.length;i++){
promises[i].then(data=>{
processData(i,data);
},reject);
}
})
}
复制代码
2.5.4 race
race方法是选择多个promise首先执行完成的哪一个promise的执行结果返回即可。一个成功即成功
Promise.race(promises) {
return new Promise((resolve,reject)=>{
for(let i = 0;i<promises.length;i++){
promises[i].then(resolve,reject)
}
})
}
复制代码
2.6 bluebird
- bluebird是让异步方法直接promise化,使用方式
npm install bluebird
复制代码
let read = bluebird.promisify(fs.readFile);
read('./a.txt', 'utf8').then(data=>{
console.log(data);
})
复制代码
- 使异步方法fs.readFilepromise化。相当于包了一层promise
function promisify(fn) {
return function(...args){
return new Promise((resolve, reject)=>{
fn(...args,function (err,data) {
if(err) reject(err);
resolve(data);
})
})
}
}
复制代码
let r = blusebird.promisifyAll(fs);
复制代码
- 这个方法把fs上面的方法都增加了一个Async,并且promise化。
2.7 Q
- 同样可以promise化
npm install q
复制代码
let Q = require('q');
let defer = Q.defer()
复制代码
-
这里可以生成promise语法糖defer。
-
下一篇来解await async。。。