写点什么

从零开始实现一个 Promise

  • 2022-10-14
    浙江
  • 本文字数:5276 字

    阅读完需:约 17 分钟

1.Promise 产生背景及规范

众所周知,Promise 是 ES6 引入的新特性,旨在解决回调地狱。下面是一个简单的例子:控制接口调用顺序:


apiA-->apiB-->apiC。复杂的业务,开发人员会裂开。后生在此向老前辈致敬。


// 回调地狱apiA({    handleSuccess(resA){        apiB({            handleSuccess(resB){                apiC({                    handleSuccess(resC){
} }) } }) }})
复制代码


因此 Promise/A+规范应运而生,ES6 的 Promise 就是遵循规范开发出来的。

2. 同步 Promise

阅读规范可得下面几点基本要求:


  1. Promise 存在三个状态:pending(等待态)、fulfilled(成功态)、rejected(失败态)

  2. pending 为初始态,并可以转化为 fulfilled 和 rejected

  3. 成功时,不可转为其他状态,且必须有一个不可改变的值(value)

  4. 失败时,不可转为其他状态,且必须有一个不可改变的原因(reason)

  5. new Promise(executor=(resolve,reject)=>{resolve(value)}),resolve(value)将状态置为 fulfilled

  6. new Promise(executor=(resolve,reject)=>{reject(reson)}),reject(reson)将状态置为 rejected

  7. 若是 executor 运行异常执行 reject()

  8. thenable:then(onFulfilled, onRejected)

  9. onFulfilled:status 为 fulfilled,执行 onFulfilled,传入 value

  10. onRejected:status 为 rejected,执行 onRejected,传入 reason


// 1.Promise存在三个状态:pending(等待态)、fulfilled(成功态)、rejected(失败态)const STATUS_PENDING = 'pending'const STATUS_FULFILLED = 'fulfilled'const STATUS_REJECTED = 'rejected'class myPromise {  constructor(executor) {      // pending为初始态,并可以转化为fulfilled和rejected    this.status = STATUS_PENDING    this.value = '' // 3    this.reason = '' // 4
let resolve = value => { // 5. if (this.status === STATUS_PENDING) { this.status = STATUS_FULFILLED this.value = value } } let reject = reason => { //6. if (this.status === STATUS_PENDING) { this.status = STATUS_REJECTED this.reason = reason } } // 7. try { executor(resolve, reject); } catch (err) { reject(err); } } // 8. then(onFulfilled = () => {}, onRejected = () => {}) { // 8.1 if (this.status === STATUS_FULFILLED) { onFulfilled(this.value) } // 8.2 if (this.status === STATUS_REJECTED) { onRejected(this.reason) } }}new myPromise(resolve => { console.log('before resolve') resolve(1)}).then(res => { console.log(res)})
new myPromise((resolve, reject) => { console.log('before reject') reject('reject error')}).then(res => { console.log(res)}, error => { console.log(error)})
复制代码

3. 异步 Promise

new myPromise(resolve => {  console.log('before resolve')  setTimeout(()=>{      resolve(1)  },1000)}).then(res => {  console.log(res)})
复制代码


promise 的状态只能在 resolve 或者 reject 的时候改变,同步代码执行到 then 回调的时候 promise 的状态还是 pending,明细不符合我们的期望。


如果换做是你,你会怎么解决这个问题?举个栗子,你正在处理一堆事情(pending 状态),然后(then),老板曰: 一秒内做完手上的事来一下我办公室,做不完滚蛋。你怕忘记,一般会用清单记录 onResolvedCallbacks = ['做完了手上的事,去老板办公室'],onRejectedCallbacks = ['做不完,滚蛋'],1 秒后看完成结果,再依据选择下一步。


完善后的代码如下:参考 前端手写面试题详细解答


const STATUS_PENDING = 'pending'const STATUS_FULFILLED = 'fulfilled'const STATUS_REJECTED = 'rejected'class myPromise {  constructor(executor) {    this.status = STATUS_PENDING    this.value = ''    this.reason = ''
// 成功存放的数组 this.onResolvedCallbacks = []; // 失败存放法数组 this.onRejectedCallbacks = [];
let resolve = value => { if (this.status === STATUS_PENDING) { this.status = STATUS_FULFILLED this.value = value // pending->fulfilled 按照成功清单执行 this.onResolvedCallbacks.forEach(fn => fn()) } } let reject = reason => { if (this.status === STATUS_PENDING) { this.status = STATUS_REJECTED this.reason = reason // pending->rejected 按照异常清单执行 this.onRejectedCallbacks.forEach(fn => fn()); } } try { executor(resolve, reject); } catch (err) { reject(err); } } then(onFulfilled = () => {}, onRejected = () => {}) { if (this.status === STATUS_FULFILLED) { onFulfilled(this.value) } if (this.status === STATUS_REJECTED) { onRejected(this.reason) } // 忙碌状态,先记录老板吩咐的内容 if (this.status === STATUS_PENDING) { // onFulfilled传入到成功数组 this.onResolvedCallbacks.push(() => onFulfilled(this.value)) // onRejected传入到失败数组 this.onRejectedCallbacks.push(() => onRejected(this.reason)) } }}// 异步new myPromise((resolve, reject) => { console.log('老板曰: 一秒做完手上的事来一下我办公室,做不完滚蛋') setTimeout(() => { if (false) { // 臣妾做不到啊 resolve('做完了手上的事,去老板办公室') } else { reject('做不完,滚蛋') } }, 1000)}).then(res => { console.log(`1s 后:${res}`)}, error => { console.log(`1s 后:${error}`)})// 老板曰: 一秒做完手上的事来一下我办公室,做不完滚蛋// 1s 后:做不完,滚蛋
复制代码

4. new Promise().then().then()...

这个思路倒是挺简单,就是 then 函数返回值为另一个 Promise 实例。根据规范修改后如下:


const PENDING = 'pending'const FULFILLED = 'fulfilled'const REJECTED = 'rejected'
function resolvePromise(promise2, x, resolve, reject) { // 循环引用报错 if (x === promise2) { // reject报错 return reject(new TypeError('Chaining cycle detected for promise')); } // 防止多次调用 let called; // x不是null 且x是对象或者函数 if (x != null && (typeof x === 'object' || typeof x === 'function')) { try { // A+规定,声明then = x的then方法 let then = x.then; // 如果then是函数,就默认是promise了 if (typeof then === 'function') { // 就让then执行 第一个参数是this 后面是成功的回调 和 失败的回调 then.call(x, y => { // 成功和失败只能调用一个 if (called) return; called = true; // resolve的结果依旧是promise 那就继续解析 resolvePromise(promise2, y, resolve, reject); }, err => { // 成功和失败只能调用一个 if (called) return; called = true; reject(err); // 失败了就失败了 }) } else { resolve(x); // 直接成功即可 } } catch (e) { // 也属于失败 if (called) return; called = true; // 取then出错了那就不要在继续执行了 reject(e); } } else { resolve(x); }}
class Promise { constructor(executor) { this.state = PENDING this.value = '' this.reason = ''
// 成功存放的数组 this.onResolvedCallbacks = []; // 失败存放法数组 this.onRejectedCallbacks = [];
let resolve = value => { if (this.state === PENDING) { this.state = FULFILLED this.value = value // pending->fulfilled 按照成功清单执行 this.onResolvedCallbacks.forEach(fn => fn()) } } let reject = reason => { if (this.state === PENDING) { this.state = REJECTED this.reason = reason // pending->rejected 按照异常清单执行 this.onRejectedCallbacks.forEach(fn => fn()); } } try { executor(resolve, reject); } catch (err) { reject(err); } } then(onFulfilled, onRejected) { // onFulfilled如果不是函数,就忽略onFulfilled,直接返回value onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value; // onRejected如果不是函数,就忽略onRejected,扔出错误 onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err }; let promise2 = new Promise((resolve, reject) => { if (this.state === FULFILLED) { // 异步解决: // onRejected返回一个普通的值,失败时如果直接等于 value => value, // 则会跑到下一个then中的onFulfilled中, setTimeout(() => { try { let x = onFulfilled(this.value); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }, 0); }; if (this.state === REJECTED) { setTimeout(() => { try { let x = onRejected(this.reason); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }, 0); }; if (this.state === PENDING) { this.onResolvedCallbacks.push(() => { setTimeout(() => { try { let x = onFulfilled(this.value); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }, 0); }); this.onRejectedCallbacks.push(() => { setTimeout(() => { try { let x = onRejected(this.reason); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }, 0) }); }; }); return promise2; }}new Promise(resolve => { console.log(0) setTimeout(() => resolve(1), 3000) }) .then(res => { console.log(res) return new Promise(resolve => { console.log(2) setTimeout(() => { resolve(3) }, 3000) }) }) .then(res => { console.log(res) })
复制代码

5. catch、resolve、reject、race 和 all

1. catch(特殊的 then 方法)

catch(fn){    return this.then(null,fn)}
复制代码

2. resolve(resolve 一个值)

Promise.resolve = val => new Promise(resolve=> resolve(val))
复制代码

3. reject(reject 一个值)

Promise.reject = val => new Promise((resolve,reject)=> reject(val))
复制代码

4. race

Promise.race([p1, p2, p3])里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态 。


Promise.race = promises =>  new Promise((resolve, reject) =>    promises.forEach(pro => pro.then(resolve, reject))  )
复制代码

5. all

Promise.all 可以将多个 Promise 实例包装成一个新的 Promise 实例。同时,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,而失败的时候则返回最先被 reject 失败状态的值。


Promise.all = function (promises) {  return new Promise((resolve, reject) => {    let index = 0;    let result = [];    if (promises.length === 0) {      resolve(result);    } else {      function processValue(i, data) {        result[i] = data;        if (++index === promises.length) {          resolve(result);        }      }      for (let i = 0; i < promises.length; i++) {        //promises[i] 可能是普通值        Promise.resolve(promises[i]).then((data) => {          processValue(i, data);        }, (err) => {          reject(err);          return;        });      }    }  });}
复制代码


本文旨在记录学习过程,通过手打代码加深印象

用户头像

还未添加个人签名 2022-07-31 加入

还未添加个人简介

评论

发布
暂无评论
从零开始实现一个Promise_JavaScript_helloworld1024fd_InfoQ写作社区