黑马程序员技术交流社区
标题:
【石家庄校区】promise是什么
[打印本页]
作者:
hfy198701
时间:
2019-8-15 23:39
标题:
【石家庄校区】promise是什么
promise是什么
promise是一个对象
“承诺将来会执行”的对象在JavaScript中称为Promise对象
Promise 是一个关联了执行任务的承诺,当你的任务完成时,会根据任务的成功与否,执行相应的操作。
用法
new Promise((resolve, reject) => setTimeout(resolve, 1000, 'foo')).then()实例
function test(resolve,reject) { setTimeout(()=>{ if(true){ resolve({data:100}) }else{ reject({data:0}) } },2000) } let p1 = new Promise(test) p1.then( res =>{ console.log(res) //{data:100} } )上述实例中test就是要执行的任务,当你的任务完成时会根据成功与否,执行相应的操作
如果成功会通过调用resolve回调,并会把执行的结果作为参数,然后可以再then里面做相应的操作
有没有很熟悉的感觉,axios和ajax都是基于promisede http库,内部都是返回了一个new Promise()对象
在使用 Promise 的时候,我们最简单的理解与用法就是像上面的代码那样,把异步结果提供给 resolve 作参数,然后通过给 then 方法传递一个自定义函数作为结果处理函数。但 resolve 和 reject 这两个参数到底是什么?在这背后,它的基本工作方式到底是怎样的呢?让我们从规范的角度来初步了解它吧。
知识点
promise 的工作机制与 callback 类似,都是异步
Promise 构造函数里的 resolve/reject 函数是内部创建的,在调用它们时传入的参数就是要解析的结果
Promise.prototype.then 根据当前的 promise 的状态来决定是立即将 promise 中存储的结果取出并和参数中的处理函数一起直接插入到 Job 队列中还是先与 promise 关联起来作为结果处理函数。then 会隐式调用 Promise 构建函数构建新的 promise 并返回。
Promise.all 先创建一个新的 promise,然后先、初始化一个空的结果数组和一个计数器来对已经 resolve 的 promise进行计数,之后会进行迭代,对于每个迭代值它都会为其创造一个promise,并设定这个promise的then为向结果数组里添加结果以及计数器--,当计数器减至0时就会resolve最终结果。
Promise.all(test1,test2)两个执行任务都指向成功之后,返回一个数组,数组里面是两个任务的结果
Promise.race 也是会创建一个新的主 promise,之后主要是根据 promise 只能 resolve 一次的限制,对于每个迭代值都会创造另一个promise,先resolve的也就会先被主 promise resolve 返回结果。返回晚的自然被废弃不用
疑问点
关于promise有问题的点
Promise 对象有三种状态:pending、fullfilled 和 rejected,分别代表了 promise 对象处于等待、执行成功和执行失败状态。创建 promise 对象后处于pending状态,pending状态可以转化为fullfilled或rejected状态,但不能逆向转化,且转化过程只能有一次,即resolve或reject后不能再resolve或reject。因此需要在 promise 对象中持有状态的引用,通过添加一个名为_state(为了说明是内部属性,用户不要直接使用,属性名前加了下划线,后面同理)的属性实现。现在 Promise 构造函数定义如下:
function Promise(resolver) { this._status = 'pending';}任务(resolver)内封装了需要执行的异步操作(当然,也可以是同步操作)。同时resolver调用时会被传递两个参数:resolve和reject函数,来自于 Promise 内部的封装,分别代表任务执行成功或者失败时需要执行的操作。任务成功与否由调用者控制,且需要在成功或失败时调用resolve或reject函数,以此来标识当前 promise 对象的完成,并会触发后续 promise 的执行。
在调用Promise构造函数时,resolver会被立即调用。因此,现在Promise构造函数如下:
function Promise(resolver) { this._status = 'pending'; resolver(resolve, reject); ...}Promise 代表着一个承诺。作为承诺,总需要有一个结果,无论成功与否。如果成功,我们会获得需要的结果;当然也有可能会失败。因此我们需要在这个承诺在未来某个时刻有结果时,分别针对结果的成功或失败做相应的处理。因此 Promise 中提供了then方法来完成这个任务。then方法接收两个参数:onResolve和onReject,分别代表当前 promise 对象在成功或失败时,接下来需要做的操作。现实生活中,人们总系喜欢给出各种许诺,同样在代码的世界里,我们也经常会有一连串前后依赖的 promise 需要执行,如下面的调用方式:promise.then().then()...。因此为了方便链式调用,then方法的实现中,都会返回一个新的 promise 对象,就像 jQuery 的方法中一般都会将自己(this)返回一样(不同的是,jQuery中返回的是自身,但在 Promise 中,返回的是一个新的 promise 对象。如果此处也返回自身的话,则串行操作就变成并行操作了,显然不符合我们的目标)。因此,then方法的定义如下:
Promise.prototype.then = function(onResolve, onReject) { var promise = new Promise(function() {}); ... return promise;}此处then方法内创建的 promise 对象和暴露给用户直接调用的 Promise 构造函数所创建的 promise 对象有些不同。用户调用 Promise 构造函数时需要传递resolver参数代表与此 promise 对象关联的任务,且任务会立即执行。在未来某个时刻,用户根据任务执行的结果来判断任务是成功还是失败,并且需要调用resolver中被传入的参数resolve或reject来结束此 promise,并由此触发下一个 promise(即当前 promise 对象调用then方法所创建的 promise 对象)所关联的任务的执行。由此可知以下两点:首先then方法中创建的 promise 关联的任务不能在 promise 对象创建时立即执行,所以先传入一个空函数以符合 Promise 构造函数调用格式;其次前一个 promise 对象需要能够知道下一个 promise 对象是谁,其关联的任务是什么,这样才能在自己完成后调用下一个 promise 的任务。因此前一个 promise 需要持有下一个 promise 以及其任务的引用。由于 promise 的执行可能会成功也可能会失败,因此后一个 promise 一般会提供成功或失败后需要执行的任务供前一个 promise 调用。因此前一个 promise 持有下一个 promise 的任务引用时需要区分这一点。promise 的调用不一定都如promise.then().then()...这样的串行方式,也可以有如下的并行方式:
var promise = new Promise(xxx); promise.then(); promise.then();此时当前一个 promise 对象完成后,会同时调用两个then方法中创建的 promise 关联的任务。因此,前一个 promise 对象可能需要持有多个 promise 对象以及它们关联的成功和失败任务的引用。因此需要给 promise 对象添加属性用于这些数据的记录
此处我采用了闭包的方式实现:在 promise 对象中增加分别代表成功回调和失败回调的两个数组,数组中的每一项是通过内部封装的闭包函数调用的结果,也是一个函数。只不过这个函数可以访问到内部调用闭包时传递的 promise 对象,因此通过这种方式也可以访问到我们需要的下一个 promise 以及其关联的成功、失败回调的引用。所以现在有两处改动。首先需要在 Promise 构造函数中增加两个属性。现在 Promise 构造函数的定义如下:
function Promise(resolver) { this._status = 'pending'; this._doneCallbacks = []; this._failCallbacks = []; resolver(resolve, reject); ...}Promise.prototype.then = function(onResolve, onReject) { var promise = new Promise(function() {}); this._doneCallbacks.push(makeCallback(promise, onResolve, 'resolve')); this._failCallbacks.push(makeCallback(promise, onReject, 'reject')); return promise;}then方法中调用的makeCallback即上面说到的闭包函数。调用时会把 promise 对象以及相应的回调传递进去,且会返回一个新的函数,前一个 promise 对象持有返回函数的引用,这样在调用返回函数时,在函数内部就可以访问到 promise 对象以及回调函数了。由于成功回调onResolve和失败回调onReject都通过此闭包封装,所以在闭包中增加了第三个参数action,以区分是哪种回调。现在makeCallback的定义如下:
function makeCallback(promise, callback, action) { return function promiseCallback(value) { ... };}前面说过,调用构造函数创建 promise 对象时需要传递作为任务的函数resolver,resolver会被立即调用,并被传递参数resolve和reject函数,用于结束当前 promise 并触发接下来的 promise 的调用。下面将介绍resolve和reject函数的实现。
我们使用 promise,是期望在未来的某个时刻能获得一个结果,并且可用于接下来的 promise 调用。所以resolve函数需要有一个参数来接收结果(同样,promise 执行失败后,我们也希望在后续 promise 中获得此失败信息,做相应处理。所以reject函数也需要有一个参数来接收错误)。前面说过 promise 对象的状态只能由pending状态转换为fullfilled或rejected状态,且只能转换一次。所以resolve或reject时,需要判断一下状态。所以,现在resolve和reject函数的定义如下:
function resolve(promise, data) { if (promise._status !== 'pending') { return; } promise._status = 'fullfilled'; promise._value = data; run(promise); } function reject(promise, reason) { if (promise._status !== 'pending') { return; } promise._status = 'rejected'; promise._value = reason; run(promise); }resolve和reject函数也可以定义在 Promise 构造函数的 prototype 上,这样可直接通过promise.resolve(data)或promise.reject(reason)调用,不用传递第一个参数promise。但由于此函数是内部调用,为了不暴露不必要的接口给用户,所以定义为内部函数。由于执行时需要知道是 resolve 或 reject 哪一个 promise 对象,所以需要多一个名为promise参数。resolve和reject函数中首先判断了当前 promise 的状态,如果不是pending(即已经被 resolve 或 reject 过了,不再重复执行),则直接返回。然后赋予 promise 新的状态,并保存成功或失败的值。最后调用run函数。run函数用于触发接下来的 promise 的执行。run函数中需要注意的一点是,需要异步执行相关的回调函数。run函数的定义如下:
function run(promise) { // `then`方法中也会调用,所以此处仍需做一次判断 if (promise._status === 'pending') { return; } var value = promise._value; var callbacks = promise._status === 'fullfilled' ? promise._doneCallbacks : promise._failCallbacks; // Promise需要异步操作 setTimeout(function () { for (var i = 0, len = callbacks.length; i < len; i++) { callbacks
(value); } }); // 每个promise只能被执行一次。虽然`_doneCallbacks`和`_failCallbacks`用户不应该直接访问, // 但还是可以访问到,保险起见,做清空处理。 promise._doneCallbacks = []; promise._failCallbacks = []; }run函数中调用的 callback,就是前面闭包函数makeCallback返回的函数。makeCallback函数是整个代码中较复杂的部分。其实现过程基本是按照 Promises/A+规范(中文)(英文参见 Promises/A+规范(英文))中的[Promise 解决过程]部分来完成的。可参照规范部分,此处就不具体介绍了。
function makeCallback(promise, callback, action) { return function promiseCallback(value) { // 如果传递了callback,则使用前一个promise传递过来的值作为参数调用callback, // 并根据callback的调用结果来处理当前promise if (typeof callback === 'function') { var x; try { x = callback(value); } catch (e) { // 如果调用callback时抛出异常,则直接用此异常对象reject当前promise reject(promise, e); } // 如果callback的返回值是当前promise,为避免造成死循环,需要抛出异常 // 根据Promise+规范,此处应抛出TypeError异常 if (x === promise) { var reason = new TypeError('TypeError: The return value could not be same with the promise'); reject(promise, reason); } // 如果返回值是一个Promise对象,则当返回的Promise对象被resolve/reject后,再resolve/reject当前Promise else if (x instanceof Promise) { x.then( function (data) { resolve(promise, data); }, function (reason) { reject(promise, reason); } ); } else { var then; (function resolveThenable(x) { // 如果返回的是一个Thenable对象(此处逻辑有点坑,参照Promise+的规范实现) if (x && (typeof x === 'object'|| typeof x === 'function')) { try { then = x.then; } catch (e) { reject(promise, e); return; } if (typeof then === 'function') { // 调用Thenable对象的`then`方法时,传递进去的`resolvePromise`和`rejectPromise`方法(及下面的两个匿名方法) // 可能会被重复调用。但Promise+规范规定这两个方法有且只能有其中的一个被调用一次,多次调用将被忽略。 // 此处通过`invoked`来处理重复调用 var invoked = false; try { then.call( x, function (y) { if (invoked) { return; } invoked = true; // 避免死循环 if (y === x) { throw new TypeError('TypeError: The return value could not be same with the previous thenable object'); } // y仍有可能是thenable对象,递归调用 resolveThenable(y); }, function (e) { if (invoked) { return; } invoked = true; reject(promise, e); } ); } catch (e) { // 如果`resolvePromise`和`rejectPromise`方法被调用后,再抛出异常,则忽略异常 // 否则用异常对象reject此Promise对象 if (!invoked) { reject(promise, e); } } } else { resolve(promise, x); } } else { resolve(promise, x); } }(x)); } } // 如果未传递callback,直接用前一个promise传递过来的值resolve/reject当前Promise对象 else { action === 'resolve' ? resolve(promise, value) : reject(promise, value); } }; }
欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/)
黑马程序员IT技术论坛 X3.2