关于promise的基础知识
在接触到promise时,我自己也是一脸懵,所以两天学完后,想做一个总结关于它。
一、异步编程的背景知识
--javascript引擎是单线程时间循环的概念构建的,同一时刻只能执行一个代码块,所以需要跟踪即将运行的代码,那些代码被放在一个任务队列里,每当一段代码准备执行时,都会被添加到任务队列,每当一段代码结束执行,时间循环会执行队列中的下一个任务,队列中的任务都会从第一个执行到最后一个。
*事件模型
--用户点击按键或者按钮会触发类似onclick的事件,它会向任务队列添加一个新任务来响应用户的操作,这是js中最基础的异步编程模式,直到事件触发才执行事件处理程序,且执行时上下文与定义时相同。
--事件模型仅适用于简单的交互,然而将多个独立的异步调用连接在一起会更加复杂,所以即使适用于响应用户交互和完成类似的低频功能,但对于复杂的请求却不是很灵活。
*回调模式
--node.js通过普及回调函数来改进异步编程模式,与事件模型类似,异步代码会在未来的某个时间点执行,二者的区别是回调模式中被调用的函数是作为参数传入的。例如
readfile("example.txt",function(err,contents){
if(err){
throw err;
}
conole.log(contents);
});
console.log('Hi!');
由于使用了回调模式, readfile()函数立即开始执行,当读取磁盘上的文
件时会暂停执行。也就是说,调用 readfile()函数后, console.1og("Hi")语句立即执行并输出"Hi";当 readfile()结束执行时,会向任务队列的末尾添加一
个新任务,该任务包含回调函数及相应的参数,当队列前面所有的任务完成
后才执行该任务,并最终执行 console.1og( contents)输出所有内容。
虽然这个回调比事件模型更加的灵活,但是如果要实现复杂的功能,比如并行执行两个或多个异步操作,就需要跟踪多个回调函数并清理这些操作,比较复杂,所以这个时候,promise的用处就体现出来了。它可以把原来的回调函数分离出来,在异步操作执行完成后,用链式调用的方法执行回调函数。
二、promise的基础知识
promise相当于异步操作结果中的占位符,它不会去订阅一个事件,也不会传递一个回调函数给目标函数,而是让函数返回一个promise,实质上promise是一个构造函数。
promise表面上看是简化层层回调的方法,实质上它的精髓在于"状态",用维护状态,传递状态的方式来使得回调函数可以及时的调用,它比传递callback更加的简单、灵活。
1、promise的生命周期
先是处于进行中(pending)的状态,也是未处理的状态,一旦异步操作结束后,可能进入到以下两个状态的一种:
- Fulfilled promise异步操作完成
- Rejected 由于程序错误或一些其他的原因,异步操作未完成。
- 内部属性[PromiseState]被用来表示promise的三种状态:“pending”、“fulfilled”、“rejected”,因为这个属性不暴露在属性里,所以不能用编程的方式检测promise的状态,所以只有当promise的状态改变时,通过then()方法采取特定的行动。
2、静态方法
(1)、then()
所有的promise都有then()方法,它接收两个参数:第一个时promise的状态变成fulfilled时要调用的函数,与异步操作相关的附加数据都会传递给这个词完成函数,第二个是当promise的状态变成rejected时要调用的函数,所有与失败状态相关的附加数据都会传递给这个拒绝函数。
function getNumber(){
var p = new Promise(function(resolve, reject){
//做一些异步操作
setTimeout(function(){
var num = Math.ceil(Math.random()*10); //生成1-10的随机数
if(num<=5){
resolve(num);
}
else{
reject('数字太大了');
}
}, 2000);
});
return p;
}
getNumber()
.then(
function(data){
console.log('resolved');
console.log(data);
},
function(reason, data){
console.log('rejected');
console.log(reason);
}
);
getNumber函数用来异步获取一个数字,2秒后执行完成,如果数字小于等于5,我们认为是“成功”了,调用resolve修改Promise的状态。否则我们认为是“失败”了,调用reject并传递一个参数,作为失败的原因。
运行getNumber并且在then中传了两个参数,then方法可以接受两个参数,第一个对应resolve的回调,第二个对应reject的回调。所以我们能够分别拿到他们传过来的数据。多次运行这段代码,你会随机得到下面两种结果:
(2)、catch()方法
相当于只给其传入拒绝处理程序的then()方法。then()与catch()一起使用才能更好的处理异步操作结果。,能清楚的指明操作结果是成功还是失败,比回调函数和事件好用,如果使用事件,那么我们在遇到错误是不会主动触发的,如果是使用回调函数,则记得每次都要检查错误参数。一定要记得添加拒绝处理函数。
举一个小例子:
promise.catch(function(err){
//拒绝
console.log(err.message);
});
//与下面的调用一致
promise.then(null,function(err){
//拒绝
console.log(err.message);
});
NOTE
每次调用then()或者catch()会创建一个新任务,当promise被解决(resolved)时执行,这些任务最终会被加入到一个为promise量身定做的独立队列中!
2、创建未完成的promise
创建未处理promise的最好的办法是使用promise的构造函数,因为promise执行器具有动态性。
构造函数只接受一个参数:包含初始化promise代码的执行器函数。执行器接收两个参数,分别是resolve()函数和reject()函数。里面是一个小例子:
let fs = require('fs');
function readFile(filename){
return new Promise(function(resolve,reject){
//触发异步操作
fs.readFile(filename,{encoding:"utf-8"},function(err,contents){
//检查是否有错误
if(err){
reject(err);
return;
}
//成功读取文件
resolve(contents);
});
});
}
let promise = readFile('example.txt');
//同时监听执行完成和执行被拒
promise.then(function(contents){
//完成
console.log(contents);
},function(err){
console.log(err.message);
});
当readFile()方法被调用时,执行器会立即执行,这里有一种任务编排的过程,类似于setTimeout()和setInterval()函数,在这里---------------->
promise的执行器会立即执行,然后在执行后续的代码
具体看下面的这个例子:
let promise= new Promise(function(resolve,reject){
console.log("promise");
resolve();
});
promise.then(function(){
console.log('resolved');
});
console.log('hi~~!')
执行的结果如下:
原理分析:当调用resolve()时会触发一个异步操作,传入then()和catch()方法的函数会被添加到任务队列中并异步执行,这个例子虽然then()在consol.log('hi`~~!);之前,但其与执行器不同,所以并没有立即执行,原因在于:完成处理程序和拒绝处理程序总是在执行器完成后被添加到任务队列的末尾。
3、创建已处理的promise
以下两种方法可以根据特定的值来创建已解决promise:
(1)使用promise.resolve()
该方法只接受一个参数并返回一个完成态的promise,不会有任务编排过程,需要向promise添加一个或多个完成处理程序来获取值。例如:
let promise= Promise。resolve(42);
promise.then(function(value){
console.log(value); //42
});
因为该promise永远不存在拒绝状态,所以该promise的拒绝处理程序永远不会调用。
(2)使用promise.reject()
通过该方法来创建已拒绝promise,该promise不会调用完成处理程序。
若向上述方法传入一个promise,那么这个promise会直接被返回!
4、执行器错误
如果执行器内部抛出一个错误,则promise的拒绝处理程序就会被调用,每个执行器都隐含着一个try-catch块。---------?执行器会捕获所有的错误但只有当拒绝处理程序存在时才会记录执行器中抛出的错误,否则会被忽略。