💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
# Promise ```javascript var promise = new Promise(function(resolve,reject){ //code }) ``` ## 1.then then方法可以接受两个回调函数作为参数,第一个回调为promise状态为resolve时触发,另一个为reject时触发。 // 调用顺序 ```javascript let promise = new Promise((resolve,reject)=>{ console.log('Promise init'); resolve(); }) promise.then(()=>console.log('then is called')); console.log('hi') ``` 这里输出情况为 ``` Promise init hi then is called ``` resolve中返回promise的情况 ```html <script> var p1 = new Promise((resolve, reject) => { setTimeout(() => reject(new Error('fail')), 3000); }); var p2 = new Promise((resolve, reject) => { setTimeout(() => resolve(p1), 1000) }) p2.then(result => console.log(result)) .catch(error => console.log(error)) </script> ``` 这里p2在1秒钟后返回promise类型p1 从这时起,p2的状态就取决于p1 所以再经过两秒,即三秒钟后,触发p1的reject 由p2的catch获取。 ## 2.catch 用于返回rejecte回调 error处理。等同于 ```javascript .then(null,callback); ``` ## 3.all 将多个promise实例包装成一个新的promise实例。当所有promise为resolve时,会返回then 只要有一个rejecte,为catch。 ```javascript var p = Promise.call([p1,p2,p3]); ``` ## 4.race 若有一个实例率先改变状态,p的状态就跟着改变。 ```javascript var p = Promise.race([p1,p2,p3]); ``` ## 5.resolve ### a.参数是一个thenable对象: ```javascript let thenable = { then: (resolve, reject) => resolve(42) }; let p1 = Promise.resolve(thenable); p1.then((value) => console.log(value)); ``` 如果是一个thenable对象,resolve将该对象转换为promise对象并立即执行该对象的then方法 输出42. ### b.参数不是具有then方法的对象或者根本不是对象: ```javascript let p = Promise.resolve('Hello'); p.then(s => console.log(s)); ``` 这里判断p并不是一个thenable对象,所以将p转换为promise对象,且初始状态就是resolve,立即执行then方法 输出Hello。 ### c.不带有任何参数: 在不带有任何参数传入时,直接在本轮循环结束时生成一个promise对象。 如何定义本轮循环结束: ```javascript setTimeout((() => console.log('three')), 0); Promise.resolve().then(() => console.log('two')); console.log('one'); ``` setTimeout(fn, 0)在下一轮“事件循环”开始时执行。所以可知输出顺序为: ``` one two three ``` ## 例题 > 1. 如何按顺序调用promise > 需要注意的是,promise在创建时就会调用。所以想要顺序执行promise,那就顺序创建。所以下面方法可以顺序执行promise。 ~~~ function promiseFactory(index){ return new Promise((resolve, reject) => { resolve() console.log(index) }) } function executePromises(promisesIndex){ var result = Promise.resolve() promisesIndex.forEach(index => { result = result.then(promiseFactory(index)) }) return result } executePromises([1,2,3,4])//1,2,3,4 ~~~ 假设我们现在有这三个promise: ~~~ var a = function () { return new Promise(function (resolve, reject) { setTimeout(function () { resolve('a') }, 1000) }) } // 异步函数b var b = function (data) { return new Promise(function (resolve, reject) { resolve(data + 'b') }) } // 异步函数c var c = function (data) { return new Promise(function (resolve, reject) { setTimeout(function () { resolve(data + 'c') }, 500) }) } ~~~ 执行时,这样就可以: ~~~ var a = function () { return new Promise(function (resolve, reject) { setTimeout(function () { resolve('a') }, 2000) })     }     // 异步函数b     var b = function () { return new Promise(function (resolve, reject) { setTimeout(function () { resolve('b') }, 1000) })     }     // 异步函数c     var c = function () { return new Promise(function (resolve, reject) { setTimeout(function () { resolve('c') }, 500) })     }     let ret = [];     function getTarget(data) { ret.push(data);     }     function queue(arr) { var sequence = Promise.resolve() // 定义resolve状态的promise 后续组成promise.then队列。 arr.forEach(function (item) { sequence = sequence.then(item).then(getTarget) }); return sequence     }     // 执行队列     queue([a, b, c]).then(data => { console.log(ret);     }); ~~~ > 2. 不同状态输出不同answer > **sol** ~~~ const proposeToMissHan = (isOK) => { /* TODO */ const reply = new Promise((resolve, reject) => { const thinkTime = 35; setTimeout(() => { if (isOK) { resolve('yes') } else { reject('no'); } }, thinkTime) }) return reply; } proposeToMissHan(true).then((res)=>{ console.log(res); }).catch(err=>{ console.log(err); }) ~~~ > 3. 使用 generator 模拟 async/await > 在远古时代,我们使用 callback 进行异步流程控制,但是会有 callback hell 的问题。经过历史的发展,逐渐地使用了不少的工具进行异步流程的改进,例如 Async.js、Promise 等,到后来的 generator + promise,还有最终的方案 async/await。了解以前是用什么方案处理异步流程控制,对我们理解现在的 asyn/await 也是很有好处。 请你实现一个简单的函数 wrapAsync,使用 generator + promise 来模拟 async/await 进行异步流程的控制。wrapAsync 接受一个 generator 函数作为参数,并且返回一个函数。generator 函数内部可以使用关键字 yield 一个 Promise 对象,并且可以类似 async/await 那样获取到 Promise 的返回结果,例如: const getData = (name) => { return new Promise((resolve, reject) => { setTimeout(() => { resolve('My name is ' + name) }, 100) // 模拟异步获取数据 }) } const run = wrapAsync(function * (lastName) { const data1 = yield getData('Jerry ' + lastName) const data2 = yield getData('Lucy ' + lastName) return [data1, data2] }) run('Green').then((val) => { console.log(val) // => [ 'My name is Jerry Green', 'My name is Lucy Green' ] }) getData 是一个异步函数并且返回 Promise,我们通过 yield 关键字获取到这个异步函数的 Promise 返回的结果,在代码编写上起来像是同步的,执行上实际是异步的。 请你完成 wrapAsync 的编写,wrapAsync 返回的函数接受的参数和传入的 generator 接受的函数保持一致,并且在调用的时候会传给 generator 函数(正如上面的例子);另外,wrapAsync 返回的函数执行结果是一个 Promise,我们可以通过这个 Promise 获取到 generator 函数执行的结果(正如上面的例子)。 (此简单实现你暂时不需要考虑异常的控制。) > **sol** ~~~ const wrapAsync = (generatorFn) => { return (...args) => { const gen = generatorFn.apply(null, args); return new Promise((resolve, reject) => { let g = null; const fullFill = (res) => { g = gen.next(res); next(g); }; fullFill(); function next(ret) { if (ret.done) return resolve(ret.value); return ret.value.then(fullFill); } }) } } ~~~ ## promise实现原理 ### 1. macrotask microtask macrotask microtask 分别表示异步任务的两种分类。在挂起任务时,JS 引擎会将所有任务按照类别分到两个队列中,首先在 macrotask 的队列(也叫 task queue)中取出第一个任务,执行完毕后取出 microtask 队列中的所有任务顺序执行;之后再取 macrotask 任务,周而复始,直至两个队列的任务都取完。 ### 2. 理解 Promise 实现过程要点: 1. then方法中的回调需要异步延迟调用。虽然 Promise A+ 未明确指出是以 macrotask(task - 宏任务) 还是 microtask(微任务) 形式放入队列,但 ECMAScript 规范明确指出 Promise 必须以 Promise Job 形式加入 job queues(也就是 microtask)。 2. Promise 的调用方式是同步链式调用,因此在第一个异步任务执行前,链式调用环节产生的多个 Promise 实例便已经生成(每次 then 方法会生成一个新的 Promise 实例),并维护自己的一些内部变量(状态变量,回调处理队列,执行结果) 3. 每个 Promise 实例自身维护一个 回调处理队列,该队列存储着其下一个要调用的 then 方法相关信息,包括这个 then 方法接受的回调函数(resolve / reject)和这个 then 方法返回的新 Promise 实例。队列中可能不止包括一个元素,因为规范描述:then 方法可以被同一个 Promise 调用多次 4. 为使 Promise 的实现更具有通用性, 根据规范,传入 Promise 构造函数的 resolve 函数接受多种类型参数。 ### 3. promise 构造函数 ~~~ function Promise(fn) { this._state = 0 // 0-pending 1 resolve 2 reject this._value - null // promise 运行结果 this._deferreds = [] // 注册回调数组 try { fn(value => { resolve(this, value) }, reason => { reject(this, reason) }) } catch (err) { reject(err) } } ~~~ ### 4.then方法的实现 then 方法可以被同一个 Promise 调用多次,每次返回新 Promise 对象 。then 方法接受两个参数 onResolved、onRejected(可选)。在 Promise 被 resolve 或 reject 后,所有 onResolved 或 onRejected 函数须按照其注册顺序依次回调,且调用次数不超过一次。所以then的主要流程有一下几步: 1. 实例化空promise对象用于返回。 2. 构造then注册回调处理函数结构体 3. 判断当前状态,pending状态存储延迟处理对象,即this._deferreds.push,非pending状态执行onResolve或onReject操作。 实现如下 ~~~ function Handler(onResolve, onReject, promise) { // 封装存储onResolve onReject对象,生成新promise对象 this.onResolved = typeof onResolve === 'function' ? onResolve : null; this.onRejected = typeof onReject === 'function' ? onReject : null; this.promise = promise; } Promise.prototype.then = function (onResolve, onReject) { var res = new Promise(function () { }); var deferred = new Handler(onResolve, onReject, res); if (this._state === 0) { this._deferreds.push(deferred); return res; } handleResolve(this, deferred); return res; } ~~~ > 链式调用为什么要返回新的 promise 如我们理解,为保证 then 函数链式调用,then 需要返回 promise 实例。但为什么返回新的 promise,而不直接返回 this 当前对象呢?看下面示例代码: var promise2 = promise1.then(function (value) { return Promise.reject(3) }) 复制代码假如 then 函数执行返回 this 调用对象本身,那么 promise2 === promise1,promise2 状态也应该等于 promise1 同为 resolved。而 onResolved 回调中返回状态为 rejected 对象。考虑到 Promise 状态一旦 resolved 或 rejected就不能再迁移,所以这里 promise2 也没办法转为回调函数返回的 rejected 状态,产生矛盾。 ### 5.catch 的实现 catch是then(null,onReject)的别名 ~~~ Promise.prototype['catch'] = function (onRejected) { return this.then(null, onRejected); }; ~~~ ### 6.resolve函数 Promise 实例化时立即执行传入的 fn 函数,同时传递内部 resolve 函数作为参数用来改变 promise 状态。resolve 函数简易版逻辑大概为:判断并改变当前 promise 状态,存储 resolve(..) 的 value 值。判断当前是否存在 then(..) 注册回调执行函数,若存在则依次异步执行 onResolved 回调。 但如文初所 thenable 章节描述,为使 Promise 的实现更具有通用性,当 value 为存在 then(..) 方法的 thenable 对象,需要做 Promise Resolution Procedure 处理,规范描述为 [[Resolve]](promise, x)。(x 即 为后面 value 参数)。 具体处理逻辑流程如下: * 如果 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise * 如果 x 为 Promise ,则使 promise 接受 x 的状态 * 如果 x 为对象或函数 1. 把 x.then 赋值给 then 2. 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise 3. 如果 then 是函数,将 x 作为函数的作用域 this 调用之。 4. 如果 x 不为对象或者函数,以 x 为参数执行 promise。 ~~~ function resolve (promise, value) { // 非 pending 状态不可变 if (promise._state !== 0) return; // promise 和 value 指向同一对象 // 对应 Promise A+ 规范 2.3.1 if (value === promise) { return reject( promise, new TypeError('A promise cannot be resolved with itself.') ); } // 如果 value 为 Promise,则使 promise 接受 value 的状态 // 对应 Promise A+ 规范 2.3.2 if (value && value instanceof Promise && value.then === promise.then) { var deferreds = promise._deferreds if (value._state === 0) { // value 为 pending 状态 // 将 promise._deferreds 传递 value._deferreds // 偷个懒,使用 ES6 展开运算符 // 对应 Promise A+ 规范 2.3.2.1 value._deferreds.push(...deferreds) } else if (deferreds.length !== 0) { // value 为 非pending 状态 // 使用 value 作为当前 promise,执行 then 注册回调处理 // 对应 Promise A+ 规范 2.3.2.2、2.3.2.3 for (var i = 0; i < deferreds.length; i++) { handleResolved(value, deferreds[i]); } // 清空 then 注册回调处理数组 value._deferreds = []; } return; } // value 是对象或函数 // 对应 Promise A+ 规范 2.3.3 if (value && (typeof value === 'object' || typeof value === 'function')) { try { // 对应 Promise A+ 规范 2.3.3.1 var then = obj.then; } catch (err) { // 对应 Promise A+ 规范 2.3.3.2 return reject(promise, err); } // 如果 then 是函数,将 value 作为函数的作用域 this 调用之 // 对应 Promise A+ 规范 2.3.3.3 if (typeof then === 'function') { try { // 执行 then 函数 then.call(value, function (value) { resolve(promise, value); }, function (reason) { reject(promise, reason); }) } catch (err) { reject(promise, err); } return; } } // 改变 promise 内部状态为 `resolved` // 对应 Promise A+ 规范 2.3.3.4、2.3.4 promise._state = 1; promise._value = value; // promise 存在 then 注册回调函数 if (promise._deferreds.length !== 0) { for (var i = 0; i < promise._deferreds.length; i++) { handleResolved(promise, promise._deferreds[i]); } // 清空 then 注册回调处理数组 promise._deferreds = []; } } ~~~ ### 7. reject实现 ~~~ function reject (promise, reason) { // 非 pending 状态不可变 if (promise._state !== 0) return; // 改变 promise 内部状态为 `rejected` promise._state = 2; promise._value = reason; // 判断是否存在 then(..) 注册回调处理 if (promise._deferreds.length !== 0) { // 异步执行回调函数 for (var i = 0; i < promise._deferreds.length; i++) { handleResolved(promise, promise._deferreds[i]); } promise._deferreds = []; } } ~~~ ### 8. handleResolved handleResolved 函数具体会根据 Promise 实例当前状态判断调用 onResolved、onRejected 中的一个,处理 then(..) 注册回调为空情形,以及维护链式 then(..) 函数后续调用。 ~~~ function handleResolved (promise, deferred) { // 异步执行注册回调 asyncFn(function () { var cb = promise._state === 1 ? deferred.onResolved : deferred.onRejected; // 传递注册回调函数为空情况 if (cb === null) { if (promise._state === 1) { resolve(deferred.promise, promise._value); } else { reject(deferred.promise, promise._value); } return; } // 执行注册回调操作 try { var res = cb(promise._value); } catch (err) { reject(deferred.promise, err); } // 处理链式 then(..) 注册处理函数调用 resolve(deferred.promise, res); }); } ~~~ ## Promise的相关API实现 ### resolve > ① 参数是一个 Promise 实例 如果参数是 Promise 实例,那么Promise.resolve将不做任何修改、原封不动地返回这个实例。 ② 参数是一个thenable对象 thenable对象指的是具有then方法的对象,比如下面这个对象,例如: let thenable = { then: function(resolve, reject) { resolve(42); } }; Promise.resolve方法会将这个对象转为 Promise 对象,然后就立即执行thenable对象的then方法。 ③ 参数不是具有then方法的对象,或根本就不是对象 如果参数是一个原始值,或者是一个不具有then方法的对象,则Promise.resolve方法返回一个新的 Promise 对象,状态为resolved。 ④ 不带有任何参数 Promise.resolve方法允许调用时不带参数,直接返回一个resolved状态的 Promise 对象。 ~~~ function valuePromise(value) { var p = new Promise(function(){}); p._state = 1; p._value = value; return p; } Promise.resolve = function (value) { if (value instanceof Promise) return value; if (typeof value === 'object' || typeof value === 'function') { try { var then = value.then; if (typeof then === 'function') { return new Promise(then.bind(value)); } } catch (ex) { return new Promise(function (resolve, reject) { reject(ex); }); } } return valuePromise(value); }; ~~~ ### Promise.reject > Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected。 ~~~ Promise.reject = function (value) { return new Promise(function (resolve, reject) { reject(value); }); }; ~~~ ### Promise.race `const p = Promise.race([p1, p2, p3]);` > Promise.race方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。 上面代码中,只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。 ~~~ Promise.race = function (values) { return new Promise(function (resolve, reject) { values.forEach(function(value) { Promise.resolve(value).then(resolve, reject); }); }); }; ~~~