Promise

177 阅读9分钟

Promise:承诺者模式,它是ES6新增的一个内置类,基于Promise可以有效管理“异步编程”,避免回调地狱

  • new Promise
  • 需求:三个ajax请求,我们要实现ajax的“串行”「上一个请求成功,才可以发送下一个请求;同理还有一个ajax‘并行’:多个请求可以同时发送(偶尔需要等所有并行请求成功后,统一做什么事情)
  • 基于ajax的同步操作,实现出来ajax的串行,但是真实项目中是不允许使用ajax同步处理的(因为请求没成功之前,所有的其它事情都被阻碍了,也无法实现ajax的并行)
    • async: false:代表同步,若为true则为异步
// let data = null;
// $.ajax({
//     url: './api/data1.json',
//     async: false,
//     success(result) {
//         data = result;
//     }
// });
// console.log(`第一个${data}`);
// $.ajax({
//     url: './api/data2.json',
//     async: false,
//     success(result) {
//         data = result;
//     }
// });
// console.log(`第二个${data}`);
  • JQ中的ajax管理是基于回调函数的方式管理的「请求成功后,会触发success回调函数执行,result就是本次请求获取的结果」;如果我们想实现ajax串行,需要把下一个请求发送,放在上一个请求成功的回调函数中处理,如果有多个串行请求就会一层一层的嵌套..=>“回调地狱”「代码看起来乱,不方便管理」
  • 痛点:传统方案中,基于回调函数的方式管理异步编程的代码,总是在异步任务可执行的时候,在他的回调函数中处理一些事情,这样很容易产生回调地狱!!
// $.ajax({
//     url: './api/data1.json',
//     success(result) {
//         console.log(`第一个${result}`);
//         $.ajax({
//             url: './api/data2.json',
//             success(result) {
//                 console.log(`第二个${result}`);
//                 $.ajax({
//                     url: './api/data3.json',
//                     success(result) {
//                         console.log(`第三个${result}`);
//                     }
//                 })
//             }
//         })
//     }

// });

Promise

  • let p=new Promise([executor])
    • p是它的实例
    • [executor]是一个函数,传递的不是函数会报错
    • p.proto===Promise.prototype
      • 私有属性:[[PromiseState]]: "pending" [[PromiseResult]]: undefined

  • promise实例的状态有三种:pending准备状态 ,fulfilled/resolved成功状态 rejected失败状态

    • 最初的的状态是pending,后期基于某些操作可以把状态改为fulfilled或者rejected(但是一旦状态变为成功或者失败,则再也不能修改其状态了),而[[PromiseResult]]存放的是成功的结果或者失败的原因!

疑惑一:状态改完有什么用?

  • 1)基于then可以存放两个函数 p.then(onfulfilled,onrejected) .
    1. 当我们把状态修改fulfilled成功状态,则会把onfulfilled这个函数执行,相反,我们把状态修改为rejected失败状态,我们会把onrejected这个方法执行...
  • 3) 并且会把[[PromiseResult]]作为实参值,传递给onfulfilled/onrejected
    • p.then啥意思?

疑惑二:咋改状态?

  • 1)new Promise的时候,会立即把传进来的executor函数执行,并且会给executor传递两个实参进来,我们会用两个形参变量接收resolve/reject,并且值是两个函数
  • 2)当我们执行resolve,promise实例状态变为fulfilled成功,传递的值就是成功的结果,赋值给[[PromiseResult]];同理,只要把reject执行,promise实例状态变为rejected失败,传递的值就是失败的原因!
  • 3)如果executor函数执行报错了,则实例的状态也是rejected失败,失败原因就是错误信息!!
let p = new Promise(function executor(resolve, reject) {
    reject('NO');
});
p.then(function onfulfilled(result) {
    console.log('成功', result);
}, function onrejected(reason) {
    console.log('失败ln', reason);
});
new Promise((resolve, reject) => {
    // 在这里一般管理异步编程的代码
    $.ajax({
        url: './api/data1.json',
        success(result) {
            resolve(result);//当我们执行resolve,promise实例状态变为fulfilled成功,传递的值就是成功的结果,赋值给[[PromiseResult]]
        },//请求成功后,会触发success回调函数执行,result就是本次请求获取的结果」
        error(reason) {
            reject(reason);
        }
    });
}).then(result => {
    console.log('请求成功', result);//当我们把状态修改fulfilled成功状态,则会把onfulfilled这个函数执行,,,并且会把[[PromiseResult]]作为实参值,传递给onfulfilled/onrejected
}, reason => {
    console.log('请求失败', reason);
});
  • 我们可以基于 “promise实例.then” 存放多个onfulfilled/onrejected方法,状态变为成功或者失败,存放的每一个对应的方法都会被执行
let p1 = new Promise((resolve, reject) => {
    resolve(100);
});
p1.then(result => {
    console.log(`成功:${result}`);
}, reason => {
    console.log(`失败:${reason}`);
});
p1.then(result => {
    console.log(`成功:${result}`);
}, reason => {
    console.log(`失败:${reason}`);
});
  • new Promise产生的实例,他的状态是成功还是失败,取决于“resolve/reject执行 或者 executor执行是否报错”
let p1 = new Promise((resolve, reject) => {
     reject(0);
 });
  • 每一次执行THEN方法,不仅存放了onfulfilled/onrejected方法,而且还会返回一个“全新的promise实例”
    • 新实例p2的状态和值由谁来决定呢?
      • 不论onfulfilled/onrejected这两个方法执行的是哪一个,我们只看执行是否报错;如果报错,则p2的状态是失败态rejected,值是报错原因;如果不报错,则p2的状态是成功态fulfilled,值是函数的返回值!
// let p2 = p1.then(result => {
//     console.log(`成功:${result}`);
//     return 1000;
// }, reason => {
//     console.log(`失败:${reason}`); //失败 0
//     return -1000;
// });
// let p3 = p2.then(result => {
//     console.log(`成功:${result}`); //成功 -1000
//     throw new Error('xxx');
// }, reason => {
//     console.log(`失败:${reason}`);
// });
// p3.then(result => {
//     console.log(`成功:${result}`);
// }, reason => {
//     console.log(`失败:${reason}`); //失败 Error:xxx
// });
  • 特殊情况:我们之前说,不论onfulfilled/onrejected执行,只要不报错,则新实例p2的状态就是成功,只要报错就是失败...但是这里有一个特殊的情况:“执行不报错,但是返回值是一个新的promise实例,这样返回值的这个promise实例是成功还是失败,直接决定了p2是成功还是失败”
// let p1 = new Promise((resolve, reject) => {
//     resolve(100);
// });
// let p2 = p1.then(result => {
//     console.log(`成功:${result}`); //成功 100
//     return new Promise((resolve, reject) => reject(-1000));
// }, reason => {
//     console.log(`失败:${reason}`);
//     return -1000;
// });
// p2.then(result => {
//     console.log(`成功:${result}`);
// }, reason => {
//     console.log(`失败:${reason}`); //失败:-1000
// });

Promise中的then链机制

    • 因为每一次.then都会返回一个新的promise实例,所以我们就可以持续.then下去了
    • 而且因为实例诞生的方式不同,所以状态判断标准也不同
      • 第一类:new Promise 出来的实例
        • 执行的是 resolve 还是 reject 决定状态
        • executor函数执行是否报错
      • 第二类:.then 返回的新实例
        • 不论执行的是onfulfilled还是onrejected
        • 首先看返回值是否为新的promise实例,如果不是,则只看执行是否报错「不报错状态就是成功,值就是函数返回值;报错则状态就是失败,值就是失败原因」
        • 如果返回的是新的promise实例,则新的promise实例的状态和值,直接决定了.then返回的实例的状态和值
      • 第三类:
        • Promise.resolve(100) 返回一个状态是成功,值是100的新promise实
        • Promise.reject(0) 返回一个状态是失败,值是0的新promise实例
        • 只要实例的状态和值我们分析好,则 .then(onfulfilled,onrejected) 存放的方法,哪一个执行我们就知道了

Promise新增

如果执行onfulfilled或者onrejected的时候,返回的值是是一个promise实例“@P”,我们之前说“@P”是成功还是失败,直接决定了“p2(.then返回的新实例)”是成功还是失败...但是这样是不严谨的,按照官方规范要求,如果@P是成功状态,需要把它的值再次处理一遍...如果@P是失败的,直接认定为失败的状态,不需要把失败的值再次处理...

Promise.resolve(10).then(result => {
    console.log(`成功:${result}`); //成功:10
    return Promise.reject(Promise.resolve(100));
}).then(result => {
    console.log(`成功:${result}`);
}, reason => {
    console.log(`失败:${reason}`); //失败:[object Promise]
});
  • then链的穿透(顺延)
    • then链的穿透性(顺延)”:正常情况下,.then的时候会传递两个函数onfulfilled/onrejected,但是有些时候,我们是不传递其中的某个函数的,这种情况下我们需要采取“顺延策略”:找到下一个then中对应状态的函数执行
      • 例如:.then(null,onrejected) 或者 .then(onfulfilled)
Promise.reject(0).then(result => {
    console.log(`成功:${result}`);
    return 1;
}).then(result => {
    console.log(`成功:${result}`);
    return 2;
}).then(result => {
    console.log(`成功:${result}`);
    return 3;
}, reason => {
    console.log(`失败:${reason}`); //失败:0
});


Promise.resolve(100).then(result => {
    console.log(`成功:${result}`); //成功:100
    throw '我失败了';
}).then(result => {
    console.log(`成功:${result}`);
    return 2;
}).then(result => {
    console.log(`成功:${result}`);
    return 3;
}, reason => {
    console.log(`失败:${reason}`); //失败:我失败了
}); */

/* Promise.resolve(100).then(result => {
    console.log(`成功:${result}`); //成功:100
    return 1;
}).then(result => {
    console.log(`成功:${result}`); //成功:1
    return Promise.reject('NO');
}).then(null, reason => {
    console.log(`失败:${reason}`); //失败:NO
});
  • catch
    • 真实项目中,我们经常:then中只传递onfulfilled,处理状态是成功的事情;在then链的末尾设置一个catch,处理失败的事情(依托于then链的穿透机制,无论最开始还是哪个then中,出现了让状态为失败的情况,都会顺延到最末尾的catch部分进行处理)
    • .catch(onrejected) ==> .then(null,onrejected)
Promise.resolve(100).then(result => {
    console.log(`成功:${result}`); //成功:100
    return 1;
}).then(result => {
    console.log(`成功:${result}`); //成功:1
    return Promise.reject('NO');
}).catch(reason => {
    console.log(`失败:${reason}`); //失败:NO
}); 
  • JS中的异常捕获
    • JS中的异常捕获:try中尝试执行代码,如果执行不报错,则不走catch,如果执行报错,直接进入到catch中,而且err接收的就是报错信息...但是不论报错与否,finally都会执行
try {
    console.log(a);
} catch (err) {
    console.log(err); //ReferenceError: a is not defined
} finally {
    console.log('哈哈,我是个傻子'); //哈哈,我是个傻子
} */
/* try {
    let a = 10;
    console.log(a); //10
} catch (err) {
    console.log(err);
} finally {
    console.log('哈哈,我是个傻子'); //哈哈,我是个傻子
} 
  • 若没有进行try....catch处理
console.log(a); //Uncaught ReferenceError: a is not defined 报错了,下面代码则不再执行
console.log('OK');
  • TRY/CATCH的作用:
    • 捕获异常信息,不影响下面的代码执行;还可以收集错误信息,发送给后台做统计等
try {
    console.log(a);
} catch (err) {}
console.log('OK'); //OK 

Promise中的并行

执行FN1返回一个promise实例,实例中管理了一个异步编程的代码,当定时器到时间后,才会把实例的状态改为成功

const fn1 = () => {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve(1);
        }, 1000);
    });
};
const fn2 = () => {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve(2);
        }, 2000);
    });
};
const fn3 = () => {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve(3);
        }, 3000);
    });
}; */

// 异步的“并行”:同时处理,相互之间没啥依赖
/* fn1().then(result => {
    console.log(result);
});
fn2().then(result => {
    console.log(result);
});
fn3().then(result => {
    console.log(result);
});
  • 异步的并行:同时处理,相互之间没有什么依赖,但是并行中的综合处理:一起发送多个请求(处理多个异步),但是需要等到所有异步都成功,我么再整体做啥事!!
    • let promise = Promise.all([promise1,promise2,...]);
      • 执行Promise.all返回一个新的promise实例 @P
      • 并且传递一个数组,数组中包含N多其它的promise实例
      • 如果数组中的每一个promise实例最后都是成功的,则@P也将会是成功的,它的值也是一个数组,按照顺序依次存储各个promise实例的结果;但凡数组中的某个promsie实例是失败的,则@P也是失败的,值是当前这个实例失败的原因!
      • 如果数组中有一项并不是promise实例(例如:是个100),则浏览器也会把其默认变为一个状态是成功的promsie实例,值就是当前项本身
let p = Promise.all([Promise.resolve(100), fn1(), 200, fn3(), fn2()]);
p.then(results => {
    console.log(`成功:${results}`); // 成功:100,1,200,3,2 顺序和最开始是一致的,不会考虑谁先成功
}).catch(reason => {
    console.log(`失败:${reason}`);
}); */

/* let p = Promise.all([Promise.resolve(100), fn1(), new Error('xxx'), Promise.reject(200), fn3(), fn2()]);
p.then(results => {
    console.log(`成功:${results}`);
}).catch(reason => {
    console.log(`失败:${reason}`); //失败:200
});
      • 上面代码中抛了一个错误,但是在all方法中,它不是promise的实例,会默认把它变为promise实例,并且值是当前项本身,所以catch中不会拿到这个
 Promise.all([fn1(), fn2(), fn3()]).then(results => {
    console.log(`三个异步都成功了,分别的结果:${results}`);
});

Promise异步的串行

  • 异步的“串行”:第一个异步成功才能发送第二个,第二个成功才能发送第三个....多个异步之间一般是有依赖的
fn1().then(result => {
    console.log(`第一个成功:${result}`);
    return fn2();
}).then(result => {
    console.log(`第二个成功:${result}`);
    return fn3();
}).then(result => {
    console.log(`第三个成功:${result}`);
}).catch(reason => {
    console.log(`只要其中一个失败,直接顺延到这里,其余剩下的请求就不发送了!`);
}); 
  • promise状态是失败,如果不用catch(或者onrejected)处理,控制台会抛出异常:Uncaught (in promise) xxx,但是此异常不会阻碍下面代码执行!!

async+await

  • async修饰符
    • 修饰一个函数,让函数的返回值成为一个promise实例,这样就可以基于THEN链去处理了
    • 如果函数自己本身就返回一个promise实例,则以自己返回的为主
    • 如果函数自己本身没有返回promise,则会把返回值变为一个promise实例:状态->成功 值->返回值
    • 如果函数执行报错,则返回的实例 状态->失败 值->报错原因
    • async最主要的作用就是:如果想在函数中使用await,则当前函数必须基于async修饰
/* async function fn() {
    return 10;
}
fn().then(result => {
    console.log(result);
});
 */
/* async function fn() {
    return async function () {
        return 10;
    };
}
fn().then(result => {
    // result:async function () {...}
    return result();
}).then(result => {
    console.log(result); //10
}); */
  • await:等待
    • 等待,我们一般在其后面放promise实例,它会等待实例状态为成功,再去执行“当前上下文”中,await下面的代码「如果promise实例管控的是一个异步编程,其实它是在等待异步成功,再执行下面代码,类似于把异步改为同步效果
    • 如果后面放的不是promise实例,则浏览器默认会把其转换为“状态为成功,值就是这个值”的实例
      • await 10; --> await Promise.resolve(10);
/* const fn1 = () => {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve(1);
        }, 1000);
    });
};
(async function () {
    let result = await fn1();
    console.log(result); //下面代码可以执行,说明await后面的promise实例,它的状态已经是成功了,await的返回值就是当前promise实例的值
    console.log('OK');
})(); */
/* const fn1 = () => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            reject(0);
        }, 1000);
    });
};
(async function () {
    let result = await fn1(); //如果await后面的promise实例状态是失败的,则下面代码永远都不会执行了
    console.log(result);
    console.log('OK');
})(); */

事件循环机制 EventLoop

  • 同步代码执行,遇到一个异步任务
      1. 先把其放在 WebAPI 进行监听
      1. 当前异步任务监听到可以执行了,则再把其放在EventQueue中,排队等待执行
  • 同步任务执行完,主线程空闲下来
      1. 去EventQueue中找可执行的微任务,如果微任务中都执行完了,再去找可执行的宏任务「队列:优先级队列 & 先进先出
      1. 取到的任务都放在Stack中交给主线程去执行

promise.then(onfulfilled,onrejected)

  • 情况一:我此时已经知道promise是成功还是失败的
    • 我们此时应该去执行onfulfilled或者onrejected,但是不是立即执行,它是一个异步的微任务
    • 首先把执行对应的方法这个事情放在WebAPI中监听,但是因为此时已经知道状态了,对应的方法肯定可以执行,所以紧接着把它挪至到EventQueue中「异步微任务队列」等待执行
  • 情况二:此时的promise还是pending状态
    • 我们把onfulfilled/onrejected先存储起来,只有当后面,我们把实例的状态修改为成功/失败的时候,再取出之前存储的方法,把其执行「而且此时再执行,还是个异步微任务」
    • 还是要经历:WebAPI -> EventQueue

await中的异步:当前上下文,await下面的代码执行是异步微任务

  • 情况1:await后面的promise实例我们已知是成功的
    • 先把微任务放置在WebAPI中,但是知道是可以执行的,则直接在挪至到EventQueue中等待执行
  • 情况2:await后面的promise实例还是pending状态
    • 此时我们把微任务放置在WebAPI中监听,等到后期promise实例是成功态后,再把它挪至到EventQueue中等待执行即可\