Promise 系统学习
目录
##第一部分: 主要对于JS单线程与异步的进一步巩固
##第二部分:主要对于Promise入门、进阶、进阶的展开
第一部分 金字塔渊源
1.1 单线程之囧
JS引擎是单线程的。这意味着在任何环境中,只有一段JS代码会被执行。当JS引擎开始执行一个函数(比如回调函数)时,它就会把这个函数执行完,也就是说只有执行完这段代码才会继续执行后面的代码。
尽可能快的进行轮询,如果事件队列中有代码需要执行,它会让JS引擎执行这段代码,然后移到下一个需要执行的代码,或者等待新的代码进来。
前提已经确定: 一个一个执行,那么如何提高速度呢?总不可能像去银行排队等呀等的吧。。。。那么并发就闪亮登场了
1.2 翻身之---异步
先了解一些概念
并发: 多个任务在同一时间内【似乎同时】执行的。通过穿插地执行它们的每个原子操作。这也就很大程度上提升了速度。
如:小明,一堂课,左手可以玩手机、右手可以写作业、左耳可以听歌、右耳可以听课。。。。【小明同学、请给大家讲一下并发】答曰【一个我同一时间做这么多事,这就叫并发】【滚。。】
那【并行】是啥?就是一个线程干一件事,同时开启多个线程。
既然说了【并行】与【并发】,那么【同步】【异步】又是什么?
【同步】就比如:做饭的步骤:先烧开水-->然后洗菜-->然后放面条--。。。。如果这样的算术水平估计小学都难以毕业。【效率低下】;
【异步/事件驱动】设置一个任务后立即返回,然后加上一个监听,当任务结束的时候,就去调用监听。比如:烧水的时候同时洗菜,【耳朵】听着水是否烧开了,【如果开了】回来赶紧关火,然后在做类似的操作。不过除了【忙碌,诗和远方就真的成远方了。。。】
比如如下代码
var fs = require('fs');
fs.readFile('./test.txt', 'utf8', function (err, data) {
if (err) {
return console.log(err);
}
console.log(data);
});
console.log("11111111");
# node fs7.js
11111111
hello, world!
回到异步
单线程和异步确实不能同时成为一个语言的特性。
js选择了成为单线程的语言,所以它本身不可能是异步的,但js的宿主环境(比如浏览器,Node)是多线程的,宿主环境通过某种方式(事件驱动,下文会讲)使得js具备了异步的属性。
js是单线程语言,浏览器只分配给js一个主线程,用来执行任务(函数),但一次只能执行一个任务,
这些任务形成一个任务队列排队等候执行,但前端的某些任务是非常耗时的,比如网络请求,定时器
和事件监听,如果让他们和别的任务一样,都老老实实的排队等待执行的话,执行效率会非常的低,
甚至导致页面的假死。所以,浏览器为这些耗时任务开辟了另外的线程,主要包括http请求线程,
浏览器定时触发器,浏览器事件触发线程,这些任务是异步的。
刚才说到浏览器为网络请求这样的异步任务单独开了一个线程,那么问题来了,这些异步任务完成后,主线程怎么知道呢?
答案就是回调函数,整个程序是事件驱动的,每个事件都会绑定相应的回调函数,
我们把刚才了解的概念和图中做一个对应,上文中说到的浏览器为异步任务单独开辟的线程可以统一理解为WebAPIs,上文中说到的任务队列就是callback queue,我们所说的主线程就是有虚线组成的那一部分,堆(heap)和栈(stack)共同组成了js主线程,函数的执行就是通过进栈和出栈实现的,比如图中有一个foo()函数,主线程把它推入栈中,在执行函数体时,发现还需要执行上面的那几个函数,所以又把这几个函数推入栈中,等到函数执行完,就让函数出栈。等到stack清空时,说明一个任务已经执行完了,这时就会从callback queue中寻找下一个人任务推入栈中(这个寻找的过程,叫做event loop,因为它总是循环的查找任务队列里是否还有任务)。
当我们的程序需要大量I/O操作和用户请求时,js这个具备单线程,异步,事件驱动多种气质的语言是多么应景!相比于多线程语言,它不必耗费过多的系统开销,同时也不必把精力用于处理多线程管理,相比于同步执行的语言,宿主环境的异步和事件驱动机制又让它实现了非阻塞I/O
回调地狱。够头疼的。。。。
task1(function (value1) {
task2(value1, function(value2) {
task3(value2, function(value3) {
task4(value3, function(value4) {
// Do something with value4
// ... more task ...
// I am in the callback hell
});
});
});
});
第二部分 一步步跳出地狱
2.1 promise
Promise出现在的并不晚,其最初是在 E语言中被提出,而ECMAScript 6
中的Promise规范来源于Promises/A+社区。
Promise是一个【用于异步处理对象】,其中包含了对异步进行各种操作的组件。Promise把JavaScript中的异步处理对象和处理规则进行了规范化,并按照【统一的接口】来编写,使用规定方法之外的写法会出现错误。使用Promise规范处理异步编程,相对比较简单和易于理解。Promise迷你书下载
Promise对象已在ECMAScript 2015中做为JavaScript的标准内置对象提供,这个对象根据Promise A+规范实现,相对比较简单。
Promise的意义就在于 then 链式调用 ,它避免了异步函数之间的层层嵌套,将原来异步函数的 嵌套关系 转变为便于阅读和理解的 链式步骤关系 。
2.2 Promise基础
2.2.1 Promise 构造与基本属性
使用 new 来调用 Promise 的构造器来进行实例化一个对象。
// 异步处理
var promise = new Promise(function(resolve, reject) {
// 调用resolve(成功) / reject(失败)
});
第一个例子[简单]
var promise = new Promise(function(resolve, reject) {
// 异步处理
// 处理结束后、调用resolve 或 reject
// resolve('Async Hello world');
reject('Async Hello world');
});
promise.then(function(result){
// 获取文件内容成功时的处理
console.log(result);
}).catch(function(error){
// 获取文件内容失败时的处理
console.log(error);
});
第二个例子【稍微难点】
function asyncFunction() {
//构造器来进行实例化一个对象
return new Promise(function (resolve, reject) {
//20毫秒后执行
setTimeout(function () {
//设置成功的
resolve('Async Hello world');
}, 20);
});
}
console.log('1111111');
//获取promise对象,20毫秒时执行成功方法
//实例方法
//onFulfilled: resolve成功时调用
//onRejected: reject失败时调用
asyncFunction()
.then(function onFulfilled(value){
console.log(value);
})
.catch(function onRejected(error){
console.error(error);
});
console.log('22222222');
打印的结果
1111111
22222222
Async Hello world
2.2.2 Promise的状态
1. 【初始化状态】 "unresolved" - Pending
2. 【成功状态】"has-resolution" - Fulfilled;
3. 【失败状态】"has-resolution" - Fulfilled;
当promise的对象状态发生变化时,用** .then **来定义【只会】被调用一次的函数。
image.png【来个稍微复杂一些的】
function getURL(URL) {
//定义并返回去promise对象
return new Promise(function (resolve, reject) {
var req = new XMLHttpRequest();
req.open('GET', URL, true);
//http请求做判断200、500、error
//还是第一个例子的resolve与reject的赋值
req.onload = function () {
if (req.status === 200) {
resolve(req.responseText);
} else {
reject(new Error(req.statusText));
}
};
req.onerror = function () { reject(new Error(req.statusText));
};
req.send();
});
}
// 运行示例
var URL = "http://httpbin.org/get";
//成功与失败,由上图得出只可能有一个,
getURL(URL)
.then(function onFulfilled(value){
console.log(value);
})
.catch(function onRejected(error){
console.error(error);
});
2.3 Promise加深一点
2.3.1 快捷构造--Promise.resolve/reject
结构体构建的Promise如下所示:
var promise = new Promise(function(resolve, reject) {
resolve('hello');
});
promise.then(function(value){
console.log(value);
}).catch(function(error){
console.log(error);
})
静态方法 Promise.resolve(value) 可以认为是 new Promise() 方法的快捷方式。
Promise.resolve('hello promise')
.then(function(value){
console.log(value);
})
Promise.reject同样是近亲。
Promise.reject(new Error("BOOM!"))
.catch(function(error){
console.error(error);
});
2.3.2 为何顺序是这样呢?
顺序已经标出来了
var promise = new Promise(function (resolve){
console.log("inner promise"); // 1 resolve(42);
});
promise.then(function(value){
console.log(value); // 3
});
console.log("outer promise"); // 2
由于JavaScript代码会按照文件的【从上到下】的顺序执行,所以最开始 <1> 会执行,然后是resolve(42)被执行。这时候 promise 对象的已经变为【确定状态】,FulFilled被设置为了【42】 。promise对象已经是【确定状态】,从程序上说对回调函数进行同步调用也是行得通的。Promise也会以【异步的方式】调用该回调函数,这是在Promise设计上的【规定方针】
2.3.3 不要对异步回调函数进行同步调用
• 绝对不能对异步回调函数(即使在数据已经就绪)进行同步调用。
• 如果对异步回调函数进行同步调用的话,处理顺序可能会与预期不 符,可能带来意料之外的后果。
• 对异步回调函数进行同步调用,还可能导致栈溢出或异常处理错乱等 问题。
• 如果想在将来某时刻调用异步回调函数的话,可以使用 setTimeout 等异步API。
2.3 链式调用Promise chain
2.3.1 promise链式调用基本
function taskA(){
console.log('A');
//模拟A函数出错,
//B函数不会执行
//finalTask亦然执行
throw new Error('A 函数出错');
}
function taskB() {
console.log('B');
}
//错误处理
function onRejected(error) {
console.log(error);
}
//结束
function finalTask() {
console.log('结束');
}
var promise = Promise.resolve();
promise
.then(taskA)
.then(taskB)
.catch(onRejected)
.then(finalTask)
结果如下
A
Error: A 函数出错
at taskA (/Users/51testing/Desktop/Promise/promise4.js:4:9)
at process._tickCallback (internal/process/next_tick.js:103:7)
at Module.runMain (module.js:592:11)
at run (bootstrap_node.js:394:7)
at startup (bootstrap_node.js:149:9)
at bootstrap_node.js:509:3
结束
Task都是【相互独立的】,只是被简单调用而已。
image.png问题:假如将调用【A、B】的顺序调换一下,打印结果是否和原来一样呢?
promise
.then(taskB)
.then(taskA)
.catch(onRejected)
.then(finalTask)
2.3.2 链式调用传参
在函数的最后加上一个return就可以了将数据传送到下个链中使用了。
//平方
function doubleUp(value) {
return value * 2;
}
//➕1
function increment(value) {
return value + 1;
}
function output(value) {
console.log(value);
}
var promise = Promise.resolve(2);
promise
.then(doubleUp)
.then(increment)
.then(output)
.catch(function(error){
console.error(error);
})
【结果:5】
Promise#then 不仅仅是注册一个【回调函数】那么简单,它还会将回调函数的【返回值进行变换为promise对象并返回】,实际上 【Promise#catch 】只是promise.then(undefined, onRejected); 方 法的一个别名而已。
【敲黑板--此处是考点】
每次调用then都会返回一个新创建的promise对象,最开始学习一门新语言时总会再看完文档有个【错觉】,这么easy,但写代码时,头都是大的。【为什么????】因为例子没有返回推敲。
以下执行的结果是多少?
var promise = new Promise(function (resolve) {
resolve(100);
});
promise
.then(function (value) {
return value * 2;
});
promise
.then(function (value) {
return value * 2;
});
promise
.then(function (value) {
console.log("1: " + value);
})
【没错--100】,仔细看所有的操作都是基于【最初的】promise的,而不是链式调用的。
2.3.4 对比
如果理解了链式调用,那么下边两种写法的对比就很清晰了
function badAsyncCall() {
var promise = Promise.resolve();
promise.then(function() {
// 任意处理
return newVar;
});
return promise;
}
首先在 promise.then 中产生的异常不会被外部捕获,此外,也不能得到 then 的返回值,即使其有返回值。
这样才对
function anAsyncCall() {
var promise = Promise.resolve();
return promise.then(function() {
// 任意处理
return newVar;
});
}
。。。
参考
JavaScript Promise迷你书
bluebird
更多精彩内容请关注“IT实战联盟”哦~~~
IT实战联盟.jpg