javascript学习笔记-Promise的基本用法
javascript,这门语言我接触好久了,但对它的了解一直是:用它写的代码我都看的懂,但真写起来就很别扭,要是扣细节就更不懂了。为什么呢?一方面这类动态语言在ide上的语法提示很弱,比不了java这种静态语言,没了提示就像屠夫没了刀,废了一半!另一方面毕竟不是主力语言,用到的地方能cv别人的代码绝不自己瞎折腾。最近在看一个nodejs写的开源项目,实现的功能挺有意思的,类似自动化工作流的东西,我一直对这方面的东西感兴趣,所以关注了,看了下有些细节看不明白,因此准备好好学习下JavaScript。
期约(Promise)这个概念很抽象,简单说是为优化异步编程而生,本文也主要研究这个东西。
js的异步编程
js写的程序一般都是单线程跑的,如果没有异步的机制,所有逻辑都通过同步方式就会有很多问题,直观体现就是浏览器一直转圈圈卡着,基于这一点,js中大量用到异步编程。
js中用异步编程的方式一般是这样的:
function useAsyncDemo() {
console.log('11111111111')
setTimeout(() => {
console.log('22222222222')
}, 0)
console.log('333333333333')
}
// --------输出-------
//11111111111
//333333333333
//22222222222
js的执行引擎维护一个任务队列,在某个时机分配异步任务执行
js的回调函数
写过js代码的一般都知道回调函数这个概念,从字面意思就可以看出是:函数在某个时机将会被回调
至于什么时机,我的理解是一般是某个异步任务执行完成后再调用这个函数。有了这种设计,即可实现直到异步函数执行结束再执行某个函数逻辑这种效果。
举个例子:
// 回调函数的例子
// 有了回调函数,实现了直到异步返回结果后再执行回调函数的效果
function useCallBackDemo() {
function cb(res) {
console.log('我是一个回调函数,我接收并打印一个参数:', res);
}
setTimeout(() => {
let res = 100;
console.log('异步计算了2s得到一个结果:', res);
console.log('调用回调函数开始:', cb);
cb(res);
}, 2000);
}
// 输出
//异步计算了2s得到一个结果: 100
//调用回调函数开始: [Function: cb]
//我是一个回调函数,我接收并打印一个参数: 100
get请求的实现一般是通过异步的方式,然后搭配回调函数可获得请求的结果。有一种场景是需要嵌套调用get请求的,如:
- 请求用户信息
- 基于1的用户id请求账户信息
- 基于2的账户信息请求余额信息
要实现这种效果如果是基于回调函数的方式,不可避免地会形成回调嵌套,嵌套层数一多将形成回调地狱
如:
// 什么是回调地狱
// 体现在层层嵌套
function cbHellDemo() {
// 模拟get操作,get操作一般耗时,异步执行
// 接收一个回调函数的参数
function get(cb) {
setTimeout(() => {
let res = Math.round(Math.random() * 10);
console.log('1s后从服务端响应的数据:', res);
cb(res);
}, 1000)
}
get((res) => {
console.log('我是cb1,我获得的结果是:', res);
console.log('cb1还需继续发送get请求!');
get((res) => {
console.log('我是cb2,我获得的结果是:', res);
console.log('cb2还需继续发送get请求!');
get((res) => {
console.log('我是cb3,我获得的结果是:', res);
})
})
})
}
使用Promise优化异步编程
我对Promise的基本理解是优化异步编程,先看Promise类型的基本用法:
// Promise能够优化回调的操作
// 先看看Promise的基本特性
// 输出为1111->2222->3333
// 说明Promise构造的执行器函数会被同步执行
function basicPromiseDemo1() {
console.log('111111111111')
let p1 = new Promise(resolve => {
console.log('2222222222222')
resolve();
})
console.log('3333333333333')
}
// 输出为111111 33333333333 2222222222
// Promise的执行器函数逻辑运行到resolve时,期约会落定
// 落定后的期约会触发兑付或拒绝事件,体现在then方法中定义的相关事件被调用
// 从结果看出即使resolve先于最后的console.log('333333')执行,其事件的触发逻辑还是较靠后执行
// 原因是事件触发的执行逻辑是异步的
function basicPromiseDemo2() {
let p1 = new Promise(resolve => {
console.log('1111111111');
resolve('22222222222');
});
p1.then(value => {
console.log(value);
})
console.log('33333333333333')
}
从上面的例子可以看出Promise的then方法中定义的兑付或拒绝事件会在期约落定(resolve)后被异步执行,且能够很容易的将值传递到后续事件中,如:resolve('22222222222')
,将‘2222222222’传递到后续事件的入参中。这个特性简直是拯救回调地狱的绝佳方法!
// 通过Promise很优雅实现:等到异步计算结果返回后再执行后续逻辑 这件事。
function basicPromiseDemo3() {
new Promise(resolve => {
setTimeout(() => {
let res = 100;
console.log('异步计算了1s得到一个结果:', res);
resolve(res);
}, 1000)
}).then(value => {
console.log('从异步计算结束后接收到的值为:', value);
})
}
只要将异步操作包裹在Promise的执行器中,并在恰当时机resolve即可。
解决回调地狱的方式:
// 使用Promise解决回调地狱的问题!
// 形式上体现为链式调用,无论是阅读还是编写都友好了很多
// 一定程度上可以理解为异步转同步
function basicPromiseDemo4() {
function get() {
return new Promise(resolve => {
setTimeout(() => {
let res = Math.round(Math.random() * 10);
console.log('1s后从服务端响应的数据:', res);
resolve(res);
}, 1000);
})
}
// then方法执行返回的值必然是Promise对象,如果返回的是普通的值,如'Ok'则直接是落定状态,但如果事件中显势的返回Promise对象则以返回的Promise对象为准
get().then(value => {
console.log('then1,从服务端接收到的数据是:', value);
console.log('then1还需要继续请求服务端!');
return get();
}).then(value => {
console.log('then2,从服务端接收到的数据是:', value);
console.log('then2还需要继续请求服务端!');
return get();
}).then(value => {
console.log('then3,从服务端接收到的数据是:', value);
console.log('then3还需要继续请求服务端!');
return get();
}).then(value => {
console.log('then4,从服务端接收到的数据是:', value);
console.log('then4还需要继续请求服务端!');
return get();
}).then(value => {
console.log('then5,从服务端接收到的数据是:', value);
})
}
使用async await
的方式一定程度上使异步编程串行化,也可解决回调地狱那种问题:
// 异步转同步的另一种方式:可通过async await的方式,此为es2018中新增的特性
function asyncDemo() {
function get() {
return new Promise(resolve => {
setTimeout(() => {
let res = Math.round(Math.random() * 10);
console.log('1s后从服务端响应的数据:', res);
resolve(res);
}, 1000);
})
}
(async () => {
let res = undefined;
res = await get();
console.log(`当前时间${Date.now()},从服务端获取的数据是:${res}`);
res = await get();
console.log(`当前时间${Date.now()},从服务端获取的数据是:${res}`);
res = await get();
console.log(`当前时间${Date.now()},从服务端获取的数据是:${res}`);
res = await get();
console.log(`当前时间${Date.now()},从服务端获取的数据是:${res}`);
res = await get();
console.log(`当前时间${Date.now()},从服务端获取的数据是:${res}`);
})()
}
最后
本人javascript的水平实在不高,本文仅作为自己学习过程的笔记记录,以便后续翻看和修改,如有理解上的问题请各位看官不吝赐教!
学习过程编写的案例代码上传至github仓库:https://github.com/huanglusong/learn-javascript