号外号外:本篇文章主要使用了ES6箭头函数,如果不熟悉的同学建议看一看。嘻嘻
开篇之前先理清下面几个问题
Promise的构造函数参数可以传递什么类型?除了使用Promise构造函数进行链式调用外,Promise还有什么方式进行链式调用吗?
Promise.resolve()的参数可以传递什么类型的数据?不同类型的数据产生怎样的结果?
Promise的构造函数中调用resolve()/reject()这样做的目的是什么?
Promise的then方法是做什么的?
如何终止链式调用?(这个场景会在什么时候遇到)
如何把promise的代码转化为async await的调用方式
进阶:手写Promise(哈哈 是不是有点招人恨的样子...)
从第一个问题开始:Promise的构造函数参数必须是函数,函数有两个参数一个是resolve,一个是reject。除了Promise构造函数进行链式调用外,Promise.resolve()也可以进行链式调用,不过他的参数多数是基本数据类型会进行值穿透。
第二个问题:Promise.resolve()的参数是基本数据类型,会导致值传递
第三个问题:Promise的构造函数中调用resolve()/reject()这样做的目的是触发then的回调函数(resolve调用成功的回调,reject调用失败的回调)并且修改Promise的状态。状态有pending 转化为fulfilled或者rejected。
第四个问题:Promise的then方法可以返回上一个Promise的回到结果,也可以return一个新的Promise,从而实现链式调用
第五个问题:then返回一个空的Promise对象即可
第六个问题:
第七个问题:
解决回调地狱的代码难以维护, 常常第一个的函数的输出是第二个函数的输入这种现象 。其次promise可以支持多个并发的请求,获取并发请求中的数据
常规回调方式实现:
setTimeout(() => { console.log('1'); setTimeout(() => { console.log('2'); setTimeout(() => { console.log('3'); setTimeout(() => { console.log('4'); }, 1000); }, 1000); }, 1000); }, 1000);
promise方式实现:
function getStr1() { return new Promise((resolve, reject) => { setTimeout(function () { resolve('1'); }, 1000); }); } function getStr2() { return new Promise((resolve, reject) => { setTimeout(function () { resolve('2'); }, 1000); }); } function getStr3() { return new Promise((resolve, reject) => { setTimeout(function () { resolve('3'); }, 1000); }); } function getStr4() { return new Promise((resolve, reject) => { setTimeout(function () { resolve('4'); }, 1000); }); } getStr1().then((data) => { console.log(data); return getStr2(); }).then((data) => { console.log(data); return getStr3(); }).then((data) => { console.log(data); return getStr4(); }).then((data) => { console.log(data); })
如果我们使用Promise去实现这个效果,虽然代码量不会减少,甚至更多,但是却大大增强了其可读性和可维护性
通过上面几个问题能感受到Promise更像一个保存状态的魔法罐,只要状态改变就可以就能一直传递下去,并且状态持久的保存在自己身上,随时都可以then函数的调用。其次状态是不可逆的,只能从pending 转化为fulfilled或者rejected。总结一句话就是状态就是Promise的灵魂所在,状态使用resolve或者reject管理的。接下来通过几个例子来理解Promise
例一:
首先需要知道Promise 构造函数是同步执行的,promise.then
中的函数是异步执行的,方便下面工作进行
let promise = new Promise((resolve, reject) => { setTimeout(() => { resolve('success') }, 3000) }) promise.then((data) => { console.log(data) console.log('resolved===', promise) }) console.log('pengding====', promise)
输出结果:
如果我这里把resolve('success')注释掉会发生什么事情呢?有的同学会说then函数不再往下执行,恭喜你答对了,给你一个大大的赞!!!
就像上面七个问题中提到过的一样,resolve触发Promise状态的改变,然后触发then回调函数的调用,如果注释resolve('success'),那么整个链路就暂停了。是不是瞬间感受到状态是如何支撑Promise 运转的了。接下来再看看Promise 具有状态缓存的特性,国际惯例还是用例子引出我们的理论
let promise = new Promise((resolve, reject) => { setTimeout(() => { resolve('success') }, 3000) }) promise.then((data) => { console.log("data1===" + data) console.log('resolved===', promise) }) promise.then((data) => { console.log("data2===" + data) console.log('resolved===', promise) }) promise.then((data) => { console.log("data3===" + data) console.log('resolved===', promise) }) console.log('pengding====', promise)
只要状态从pending 转化为resolved或者rejected之后,不管我们什么时候调用,都能拿到回调结果,但是这样做有什么好处呢?
我们花费了大半篇文稿讲述Promise的状态管理,目的是什么?总不能做事情没目的吧!作为名侦探柯南的黑粉我要出来指证!!!目的就是触发then函数拿到回调结果,然后就可以为所欲为了。接下来就看看怎么拿到回调函数的
let promise = new Promise((resolve, reject) => { setTimeout(() => { resolve('success') // reject("fail") }, 1000) }).then(resolved => { console.log("resolved===" + resolved) }, rejected => { console.log("rejected===" + rejected) }) console.log('pengding====', promise)
then函数传入两个参数,第一个参数代表成功的回调,第二个参数代表失败的回调,注意参数使用函数的形式,如果参数使用基本数据类型就会出现值穿透,小伙伴后果自负哟!!!函数的参数就是我们的回调结果,是不是还是以往的那么稳妥。对于没有成功回调的我们可以使用null代替,例子
let promise = new Promise((resolve, reject) => { setTimeout(() => { // resolve('success') reject("fail") }, 1000) }).then(null, rejected => { console.log("rejected===" + rejected) return new Promise((resolve, reject) => { reject(100) }) }).then(null,reject => { console.log("=====" + reject) })
对于then的参数期望是函数,传入非函数则会发生值穿透。
Promise.resolve(1) .then(2) .then("3") .then(data => { console.log(data) })
既然我们已经知道如何通过改变Promise的状态然后拿到回调结果,那我们就很容易实现链式调用了。这里就不卖关子了,我们让then回调函数返回一个新的Promise,这样就实现了链式调用,是不是很机智!!!至此那个男孩从此不再那么天真。。。一切没有使用案例的理论都是耍流氓,这里问题不大,不就是一个案例吗???哭笑脸
let peopleObj = { name: "taowuhua", age: 18 } let promise = new Promise((resolve, reject) => { setTimeout(() => { resolve('success') }, 100) }).then(resolved => { console.log("第一次链式调用结果" + resolved) return new Promise((resolve, reject) => { resolve(JSON.stringify(peopleObj)) }) }).then(resolved => { console.log("第二次链式调用结果" + resolved) return new Promise((resolve, reject) => { reject("2") }) }, rejected => { })
一切都是那么的美好,任何人都不是 一帆风顺总会遇到挫折程序也不例外,中途遇到异常怎么办?怎么办?怎么办?我好害怕!!!这个时候catch从天而降,一切又回到原来的状态,不再害怕不再担心
let promise = new Promise((resolve, reject) => { console.log(taowuhua) }).then(resolved => { return new Promise((resolve, reject) => { resolve(1) }) }).catch(rejected => { console.log(rejected) })
到此链式调用就算说拜拜的时候到了。
then中 return 一个 error 对象并不会抛出错误,所以不会被后续的 catch捕获
let promise = new Promise((resolve, reject) => { resolve("taowuhua") }).then(resolved => { return new Error("总是抓不住那颗受伤的心...") }).catch(rejected => { console.log(rejected) })
let promise = new Promise((resolve, reject) => { resolve("taowuhua") }).then(resolved => { return new Error("总是抓不住那颗受伤的心...") }).then((resolve) => { console.log(resolve) }, (reject) => { console.log(reject) }).catch(rejected => { console.log(rejected) })
Promise.resolve
方法返回一个状态为resolved的promise实例
Promise.resolve('foo');//等价于如下 new Promise((resolve,reject)=>{ resolve('foo'); })
Promise.reject
方法返回一个状态为rejected的promise实例
Promise.reject('foo');//等价于如下 new Promise((resolve,reject)=>{ reject('foo'); })
Promise.all
function getStr1() { return new Promise((resolve, reject) => { setTimeout(function () { resolve('1'); }, 1000); }); } function getStr2() { return new Promise((resolve, reject) => { setTimeout(function () { resolve('2'); }, 1000); }); } function getStr3() { return new Promise((resolve, reject) => { setTimeout(function () { resolve('3'); }, 1000); }); } function getStr4() { return new Promise((resolve, reject) => { setTimeout(function () { resolve('4'); }, 1000); }); } Promise.all([getStr1(), getStr2(), getStr3(), getStr4()]).then((values) => { console.log(values); });
异步代码同步化并不是异步代码就变成了同步代码了,而是视觉上像同步代码在运行是的。
建议移步这个链接:https://segmentfault.com/a/1190000007535316
async 用于申明一个 function 是异步的,async 函数返回 Promise为resolved状态的构造函数。而 await 用于等待一个异步方法执行完成
async函数有返回值(类似 Promise.resolve(x)
)
async function testAsync() { return "hello async"; } const result = testAsync(); console.log(result);
async function testAsync() { return "hello async"; } testAsync().then(v => { console.log(v); // 输出 hello async });
async函数没有返回值(类似 Promise.resolve(undefined)
)
async function testAsync() { } const result = testAsync(); console.log(result);
await等什么
如果它等到的不是一个 Promise 对象,那 await 表达式的运算结果就是它等到的东西。
如果它等到的是一个 Promise 对象,await 就忙起来了,它会阻塞后面的代码,等着 Promise 对象 resolve,然后得到 resolve 的值,作为 await 表达式的运算结果
假设一个业务,分多个步骤完成,每个步骤都是异步的,而且依赖于上一个步骤的结果。我们仍然用 setTimeout
来模拟异步操作:
/** * 传入参数 n,表示这个函数执行的时间(毫秒) * 执行的结果是 n + 200,这个值将用于下一步骤 */ function takeLongTime(n) { return new Promise(resolve => { setTimeout(() => resolve(n + 200), n); }); } function step1(n) { console.log(`step1 with ${n}`); return takeLongTime(n); } function step2(n) { console.log(`step2 with ${n}`); return takeLongTime(n); } function step3(n) { console.log(`step3 with ${n}`); return takeLongTime(n); }
现在用 Promise 方式来实现这三个步骤的处理
function doIt() { console.time("doIt"); const time1 = 300; step1(time1) .then(time2 => step2(time2)) .then(time3 => step3(time3)) .then(result => { console.log(`result is ${result}`); console.timeEnd("doIt"); }); } doIt();
经历了上面的磨难可以出去社会闯荡一下了,先从下面的两个小demo试试
Promise.resolve(1) .then((res) => { return Promise.resolve(4) }) .then(5) .then((res) => 2) .then(Promise.resolve(3)) .then(data => { console.log(data) })
Promise.resolve(Promise.resolve(3)) .then((res) => { return Promise.resolve(4) }) .then(5) .then((res) => 2) .then(Promise.resolve(3)) .then(data => { console.log(data) })
这里还有一部分面试题感兴趣的同学可以研究下。直通车:https://juejin.im/post/5a04066351882517c416715d