关于fetch中使用的promise对象理解

ECMAscript 6 原生提供了 Promise 对象。

Promise 对象代表了未来将要发生的事件,用来传递异步操作的消息。

Promise 对象有以下两个特点:
1、对象的状态不受外界影响。Promise 对象代表一个异步操作,有三种状态:

pending: 初始状态,不是成功或失败状态。
fulfilled: 意味着操作成功完成。
rejected: 意味着操作失败。
只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是 Promise 这个名字的由来,它的英语意思就是「承诺」,表示其他手段无法改变。

2、一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise 对象的状态改变,只有两种可能:从 Pending 变为 Resolved 和从 Pending 变为 Rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。就算改变已经发生了,你再对 Promise 对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

理解:所以根据这两种可能,执行不同的回调方法。

对于已经实例化过的 promise 对象可以调用 promise.then() 方法,传递 resolve 和 reject 方法作为回调。
promise.then() 是 promise 最为常用的方法。

语法:promise.then(onFulfilled, onRejected);  或者  promise.then(onFulfilled).catch(onRejected)

模拟promise异步调用

1.创建promise实例

//当异步代码执行成功时,我们才会调用resolve(...), 当异步代码失败时就会调用reject(...)
//我们使用setTimeout(...)来模拟异步代码,实际编码时可能是XHR请求或是HTML5的一些API方法.
//修改true为false就会执行reject方法,执行下一个then方法中的onRejected代码块
var myFirstPromise = new Promise(function(resolve, reject){
    setTimeout(function(){
    	if(true){
			resolve("成功!");
		}else{
			reject("失败!");
		}
    }, 3000);
});

//successMessage的值是上面调用resolve(...)方法传入的值.
//successMessage参数不一定非要是字符串类型,这里只是举个例子
//failedMessage的值是上面调用reject(...)方法传入的值 
myFirstPromise.then(function(successMessage){
    console.log(successMessage + "调用resolve");
}).catch(function(failedMessage){
	console.log(failedMessage + "调用reject");
});

当then方法进行链式操作时,如果是then方法中的都是异步代码的话,不使用promise对象的resolve和reject进行控制的话,就会出现then方法中的同步代码执行完成就开始传值执行下一个then方法中的同步代码。(重点)

例如:

var promise = new Promise(function(resolve, reject) {
    setTimeout(function() {
        console.log('阶段一:异步');
        resolve(5);
    }, 3000);
});

promise.then(function(value) {
    setTimeout(function() {
        console.log('阶段二:异步,接收的值:'+value);
        return value * 2;
    }, 2000);
}).then(function(value) {
    setTimeout(function() {
        console.log('阶段三:异步,接收的值:'+value);
    }, 1000);
});

运行结果如下:
在这里插入图片描述
为什么阶段三会先运行呢,而且接收的参数是undefined?
因为在阶段一的resolve(5);执行后,promise对象的状态就由pending转成了resolve,就会执行下一个then的onFulfilled函数。然后因为在链式操作的阶段二的then中没有返回一个promise实例用于控制操作状态,所以当then方法中的同步代码块执行完毕就会直接传参到下一个then,并不会等待异步代码块执行,所以阶段二的return value*2没有执行,阶段三的then接收的参数为undefined。undefined就是阶段二同步代码块的返回值,因为没有return所以为undefined。

这里有人会觉得,阶段三取不到值是因为执行太快了,阶段三在阶段二后执行就能有值吗?
在这里插入图片描述
很明显不是。那为什么阶段三的值还是undefined呢?
其实很简单,阶段二在上一个resolve(5)传参后,阶段二的value值就已经是5了。因为阶段二没有返回一个promise对象控制操作状态,所以导致阶段二在执行执行同步代码块后,也就是设置定时器之后,直接传了undefined给阶段三,所以无论阶段三是在阶段二之前还是之后,得到的值都是undefined。

那怎么能让阶段三获取到阶段二处理后的值呢?
很简单,在阶段二的then的同步代码块中,直接return 处理后的值,这样阶段三就能获取到value,且不影响阶段二的定时器中的打印任务。

用promise对象管理异步代码块实例如下:

var promise = new Promise(function(resolve, reject) {
    setTimeout(function() {
        console.log('阶段一:异步');
        resolve(5);
    }, 3000);
});

promise.then(function(value) {
  return new Promise(function(resolve,reject){
    setTimeout(function() {
        console.log('阶段二:异步,接收的值:'+value);
        resolve(value * 2);
    }, 2000);
  })
}).then(function(value) {
    setTimeout(function() {
        console.log('阶段三:异步,接收的值:'+value);
    }, 1000);
});

执行结果如下,明细看到,异步代码被管理了起来,通过promise对象的状态去控制下一个then的执行。
在这里插入图片描述

关于promise的几个问题

resolve其实本质是onFulfilled方法,reject本质是onRejected方法,只是我们命名成这样。

1.如果resolve或reject语句后面还写了语句,会执行吗?

会执行,resolve只是promise对象用于改变状态和传参,并不影响函数内代码执行。

2.如果第一个then的回调用了promise对象,但是promise对象没写resolve或reject方法,第二个then的回调还会执行么?

不会,因为promise的对象状态一直为panding没有改变,就不会执行then中的代码。

3.如果有resolve或reject方法,但是不设参数,也就是resolve()或reject(),那么then会执行吗?

会执行,因为promise状态改变了,但是没有传参,即如果下一个then有接收参数,则参数为undefined。

4.如果第一个then的第一个回调函数没执行,第二个then的第一个回调函数会执行么?

首先要弄清楚,then的第一个回调函数和第二个回调函数分别是什么?第一个回调函数是resolve,第二个回调函数是reject。两个都是用来改变promise状态的,分别代表操作成功和操作失败,reject常常用于传错误对象信息。回到问题,答案是会。如果上一个then执行的是resovle();下一个then恰好没有resovle回调函数,那么会跳过这个then,一直向下寻找下一个then的resolve回调函数。

5.如果promise只执行了reject方法,但第一个then没有写对应的error处理回调,第二个then写了,还能处理么?

能,因为promise规定,参数可以根据当前then的链式调用一直传递下去。

6.如果流程是异步-同步-同步-同步…下去,使用promise对象是不是没有意义?

没有意义。因为promise的用途在与把异步代码按照所需要的顺序同步执行。包括全异步或者异步同步混合。

7.then方法的优先级?

测试代码如下

console.log('主线程1');

setTimeout(function() {console.log('主线程-宏队列1')}, 0);

var promise = new Promise(function(resolve, reject) {
    setTimeout(function() {console.log('Promise-宏队列1')}, 0);
    console.log('promise-主线程');
    resolve();
});

promise.then(function() {
    setTimeout(function() {console.log('Then-宏队列1')}, 0);
    console.log('then-微队列1');
}).then(function() {
    setTimeout(function() {console.log('Then-宏队列2')}, 0);
    console.log('then-微队列2');
});

setTimeout(function() {console.log('主线程-宏队列2')}, 0);

console.log('主线程2');

执行结果:
在这里插入图片描述
上面案例的执行顺序就是:

  1. 首先把整段程序放入宏队列的队首中,由主线程读取运行。
  2. console.log(‘主线程1’); //同步代码立刻执行
  3. setTimeout(function() {console.log(‘主线程-宏队列1’)}, 0); //放入宏队列等待执行0
  4. var promise = new Promise(function(resolve, reject){}; //同步代码立刻执行
  5. setTimeout(function() {console.log(‘Promise-宏队列1’)}, 0); //放入宏队列等待执行1
  6. console.log(‘promise-主线程’); //同步代码立刻执行
  7. resolve(); //回调函数,then放入微队列等待执行0
  8. console.log(‘主线程2’); //同步代码立刻执行
  9. setTimeout(function() {console.log(‘主线程-宏队列2’)}, 0); //放入宏队列等待执行2
  10. console.log(‘主线程2’); //同步代码立刻执行
  11. //执行微队列中的程序then,原因:JS引擎中存在monitoring process进程检测到主进程为空后,就会优先去微队列去队首的程序执行。
  12. setTimeout(function() {console.log(‘Then-宏队列1’)}, 0); //放入宏队列等待执行3
  13. console.log(‘then-微队列1’); //同步代码立刻执行
  14. //then链式调用
  15. setTimeout(function() {console.log(‘Then-宏队列2’)}, 0); //放入宏队列等待执行4
  16. console.log(‘then-微队列2’); //同步代码立刻执行
  17. //微队列为空,主线程为空,开始读取宏队列任务
  18. 执行宏队列0,1,2,3,4

可以看到,在实际运行过程中,最外层的同步代码优先执行,异步代码会被放入宏队列中,等到主线程执行完同步代码后,再执行微队列中的程序,最后执行宏队列的程序。先排进队列的先执行(队列是先进先出)。

Promise.prototype.catch方法:捕捉错误

Promise.prototype.catch 方法是 Promise.prototype.then(null, rejection) 的别名,用于指定发生错误时的回调函数。也就是说catch方法只有一个回调函数。

var p1 = new Promise(function(resolve, reject) {
    setTimeout(function() {
        console.log('第一个回调');
        reject(new Error("typeError","类型错误"));
    }, 3000);
});
p1.catch(function(error){
    console.log(error);
  }).then(function(){
     throw new Error("nullpointException","值不可为空")
  }).catch(function(error){
    console.log(error)
  });
1.catch()可以链式调用吗?

可以

2.可以跟.then()混合链式么?

可以

3.如果上一层的.then()没有reject,.catch()会执行吗?

可能会,如果在then中的代码块手动抛出异常,catch就会执行

4.catch()下一层如果是.then(),会执行吗?参数怎么传递?

会。这个then接收的参数是上一个then传递的值,跟上一层的catch无关。也就是说,引擎跳过不执行的代码,该怎么传递就怎么传递。

5.连写.catch()和.then(//只写一个回调)是并列关系么?

绝对不是,是执行的前后关系。从来没有什么并列关系,只有连续的或者跳跃的前后关系。连写.then(//只写一个回调)和.catch()也不是并列关系。

Promise.all方法

Promise.all 方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。

当Promise.all([p1,p2])中的p1和p2都操作成功时,Promise.all才会返回一个成功的Promise对象,且传值为p1和p2返回值的数组对象。

当Promise.all([p1,p2])中的p1或p2任意一个操作失败时,Promise.all才会返回一个失败的Promise对象,且传值为返回对象的返回值。

操作成功案例如下:

var p1 = new Promise(function(resolve, reject) {
    setTimeout(function() {
        console.log('第一个回调');
        resolve(3);
    }, 3000);
});
var p2 = new Promise(function(resolve, reject) {
    setTimeout(function() {
        console.log('第二个回调');
        resolve(2);
    }, 2000);
});
Promise.all([p1, p2]).then(function(value) {
    return new Promise(function(resolve, reject) {
        setTimeout(function() {
            console.log('第三个回调');
            console.log(value);
            resolve(value);
        }, 2000);
    });
}).then(function(value) {
    setTimeout(function() {
        console.log('第四个回调');
        console.log(value);
    }, 1000);
});

执行结果如下:
在这里插入图片描述

操作失败案例如下:

var p1 = new Promise(function(resolve, reject) {
    setTimeout(function() {
        console.log('第一个回调');
        reject(3);
    }, 3000);
});

var p2 = new Promise(function(resolve, reject) {
    setTimeout(function() {
        console.log('第二个回调');
        reject(2);
    }, 2000);
});

Promise.all([p1, p2]).catch(function(value) {
    return new Promise(function(resolve, reject) {
        setTimeout(function() {
            console.log('第三个回调');
            resolve(value * 2);
            console.log(value * 2);
        }, 2000);
    });
}).then(function(value) {
    setTimeout(function() {
        console.log('第四个回调');
        console.log(value * 2);
    }, 1000);
});

执行结果如下:
优先返回了延时较短的p2的值,所以Promise.all的catch方法中的value为2。
在这里插入图片描述

Promise.race方法

Promise.race 方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。

只要p1、p2之中有一个实例率先改变状态,Promise的状态就跟着改变。那个率先改变的Promise实例的返回值,就传递给p的返回值。

var p1 = new Promise(function(resolve, reject) {
    setTimeout(function() {
        console.log('第一个回调');
        reject(3);
    }, 3000);
});

var p2 = new Promise(function(resolve, reject) {
    setTimeout(function() {
        console.log('第二个回调');
        reject(2);
    }, 2000);
});

Promise.race([p1, p2]).catch(function(value) {
    return new Promise(function(resolve, reject) {
        setTimeout(function() {
            console.log('第三个回调');
            resolve(value * 2);
            console.log(value * 2);
        }, 2000);
    });
}).then(function(value) {
    setTimeout(function() {
        console.log('第四个回调');
        console.log(value * 2);
    }, 1000);
});

执行结果如下:
在这里插入图片描述
应用场景:可用于比赛完成后倒计时结束游戏,或者满足任意条件之一执行某个函数。

romise.resolve 方法,Promise.reject 方法

有时需要将现有对象转为Promise对象,Promise.resolve方法就起到这个作用。

如果 Promise.resolve 方法的参数,不是具有 then 方法的对象(又称 thenable 对象),则返回一个新的 Promise 对象,且它的状态为fulfilled。

var p = Promise.resolve('Hello');
 
p.then(function (s){
  console.log(s)
});
// Hello

上面代码生成一个新的Promise对象的实例p,它的状态为fulfilled,所以回调函数会立即执行,Promise.resolve方法的参数就是回调函数的参数。

如果Promise.resolve方法的参数是一个Promise对象的实例,则会被原封不动地返回。

Promise.reject(reason)方法也会返回一个新的Promise实例,该实例的状态为rejected。Promise.reject方法的参数reason,会被传递给实例的回调函数。

var p = Promise.reject('出错了');
 
p.then(null, function (s){
  console.log(s)
});
// 出错了

上面代码生成一个Promise对象的实例,状态为rejected,回调函数会立即执行。

es引擎执行队列

ES的引擎里有2个队列:

宏队列

一个叫宏队列,macrotask,也叫tasks。 一些异步任务的回调会依次进入macro task queue,等待后续被调用,这些异步任务包括:

  1. setTimeout
  2. setInterval
  3. setImmediate (Node独有)
  4. requestAnimationFrame (浏览器独有)
  5. I/O
  6. UI rendering (浏览器独有)
微队列

另一个叫微队列,microtask,也叫jobs。 另一些异步任务的回调会依次进入micro task queue,等待后续被调用,这些异步任务包括:

  1. process.nextTick (Node独有)
  2. Promise
  3. Object.observe
  4. MutationObserver
ES整个的任务队列的执行机制就是:

1.执行全局Script同步代码,这些同步代码有一些是同步语句,有一些是异步语句(比如setTimeout等);
2.全局Script代码执行完毕后,调用栈Stack会清空;
3.从微队列microtask queue中取出位于队首的回调任务,放入调用栈Stack中执行,执行完后microtask queue长度减1;
4.继续取出位于队首的任务,放入调用栈Stack中执行,以此类推,直到直到把microtask queue中的所有任务都执行完毕。注意,如果在执行microtask的过程中,又产生了microtask,那么会加入到队列的末尾,也会在这个周期被调用执行;
5.microtask queue中的所有任务都执行完毕,此时microtask queue为空队列,调用栈Stack也为空;
6.取出宏队列macrotask queue中位于队首的任务,放入Stack中执行;
7.执行完毕后,调用栈Stack为空;
重复第3-7个步骤;
重复第3-7个步骤;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值