ECMAScript 6入门Promise对象
Promise对象
刚学习完,有点粗略印象。整理记录一下以便后续学习补充,加深理解。
Promise是什么
Promise是构造函数,可以通过new来生成Promise对象。
Promise有什么用
目前我的感受是:更加方便来操作异步流程,更加明确直观的控制事件的流程以及可以链式调用
Promise特点
摘自ES6入门
Promise对象有以下两个特点。(1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。
(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
通过几个简单例子理解它
- 通过new构造一个简单的Promise对象
let p = new Promise((resolve, reject) => {});
传入的两个参数是用来控制Promise对象的状态,我们打印一下p看一下它的状态:Promise {__proto__: Promise[[PromiseStatus]]: "pending"[[PromiseValue]]: undefined
这个就是初始状态pending
而resolve,reject可以控制Promise的状态
//resolve() let p = new Promise((resolve, reject) => resolve("123")); //Promise {<resolved>: "123"}
//reject() let p = new Promise((resolve, reject) => reject("123")); //reject()后是返回一个失败状态的Promise,不需要用catch来捕获不写catch会报错 p.catch(data => console.log(data)); console.log(p); //Promise {<rejected>: "123"} 123
提到了catch那就有还有一个then
说一直白点:then(f1, f2)可以填入两个函数参数,一个参数就是将resolve中参数代入f1来执行,第二个参数将reject中参数代入f2来执行;第二个参数可以用catch来代替,并且它更加强大,catch能捕获then()中的报错let p = new Promise((resolve, reject) => { let n = Math.ceil(Math.random() * 10); n > 5 ? resolve(n) : reject(n); }); p.then( data => console.log(data), data => console.log(data), )
用catch代替,并捕获then的错误
let p = new Promise((resolve, reject) => { resolve("yes") }); p.then( data => {console.log(data),console.log(a)} ).catch(data => console.log(data)); //yes //ReferenceError: a is not defined
因为then处理后返回的还是Promise对象,这样方便链式调用,then中都没有return,怎么会有Promise对象的呢?
then或catch即使未显式指定返回值, 它们也总是自动包装一个新的fulfilled状态的promise对象。我们打印一下会发现:
Promise {<resolved>: undefined}
那么我们可以显示的return一个Promise对象看看,let p = new Promise((resolve, reject) => resolve("yes")); p.then(data => Promise.resolve("第二个Promise")).then(data => console.log(data)); //第二个Promise
可以看到
p.then(data => Promise.resolve("第二个Promise"))
返回Promise对象是Promise {<resolved>: "第二个Promise"}
并且将value值作为参数传入到第二个then中来执行Promise.resolve(value | promise | thenable)创建Promise对象
第一参数空或者原始值,创建后的Promise对象状态直接为resolved状态Promise.resolve('f') // 等价于 new Promise(resolve => resolve('f'))
第二值得注意的是具有then方法的对象
let thenable = { then :(resolve, reject) => resolve("thenable") } let p = Promise.resolve(thenable); console.log(p);
Promise对象状态由<pending>-><resolve>
第三参数为实例化的Promise对象,let p1 = new Promise((resolve, reject) => false); let p = Promise.resolve(p1); console.log(p);
p状态和p1状态一致的
Promise.reject(value)创建Promise对象
与resolve不同的是:直接将value原样作为参数传入const thenable = { then(resolve, reject) { reject('出错了'); } }; Promise.reject(thenable) .catch(e => { console.log(e === thenable) })
catch方法的参数不是reject抛出的“出错了”这个字符串,而是thenable对象。
Promise.all
(1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。(2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
将多个 Promise 实例,包装成一个新的 Promise 实例;const p = Promise.all([p1, p2, p3]);
会等all中的对象全部执行完后将数组传入回调函数then中
let p = new Promise((resolve, reject) => setTimeout(() => resolve('p'),1000)); let p1 = new Promise((resolve, reject) => setTimeout(() => resolve('p2'),2000)); let p2 = new Promise((resolve, reject) => setTimeout(() => resolve('p3'),3000)); Promise.all([p, p1, p2]).then(data => console.log(data)).catch(data => console.log(data)); // ["p", "p2", "p2"]
let p = new Promise((resolve, reject) => resolve('p')); let p1 = new Promise((resolve, reject) => reject('p2')); let p2 = new Promise((resolve, reject) => resolve('p2')); Promise.all([p, p1, p2]).then(data => console.log(data)).catch(data => console.log(data)); //p2
Promise.race
与all不同的是:只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。let p = new Promise((resolve, reject) => setTimeout(() => resolve('p'),1000)); let p1 = new Promise((resolve, reject) => setTimeout(() => resolve('p2'),2000)); let p2 = new Promise((resolve, reject) => setTimeout(() => resolve('p3'),3000)); Promise.race([p, p1, p2]).then(data => console.log(data)).catch(data => console.log(data)); //p
Promise对象的回调函数与setTimeout的顺序问题
An event loop has one or more task queues. A task queue is an ordered list of tasks, which are algorithms that are responsible for such work as: events, parsing, callbacks, using a resource, reacting to DOM manipulation…Each event loop has a microtask queue. A microtask is a task that is originally to be queued on the microtask queue rather than a task queue.
浏览器(或宿主环境) 遵循队列先进先出原则, 依次遍历macrotask queue中的每一个task, 不过每执行一个macrotask, 并不是立即就执行下一个, 而是执行一遍microtask queue中的任务, 然后切换GUI线程重新渲染或垃圾回收等.
Event Loop (事件循环)拥有如下两种队列
macrotask queue, 指的是宏任务队列, 包括rendering, script(页面脚本), 鼠标, 键盘, 网络请求等事件触发, setTimeout, setInterval, setImmediate(node)等等.
microtask queue, 指的是微任务队列, 用于在浏览器重新渲染前执行, 包含Promise, process.nextTick(node), Object.observe, MutationObserver回调等.
process.nextTick > promise.then > setTimeout ? setImmediatesetTimeout(function () { console.log('three'); }, 0); Promise.resolve().then(function () { console.log('two'); }); console.log('one'); // one // two // three
上面代码中,setTimeout(fn, 0)在下一轮“事件循环”开始时执行,Promise.resolve()在本轮“事件循环”结束时执行,console.log('one')则是立即执行,因此最先输出。
setTimeout(function() { console.log(4) }, 0); new Promise(function(resolve) { console.log(1); for (var i = 0; i < 10000; i++) { i == 9999 && resolve() } console.log(2); }).then(function() { console.log(5) }); console.log(3); //1 2 3 5 4