基础[javascript] TC39 withResolvers新特性

275 阅读2分钟

这个特性是对我们当前new Promise的一个简化写法,将原先的回调式转化为了通过返回响应的钩子函数实现实现相同的功能,

根据第三阶段提案的介绍,一些JavaScript开发者经常会自己将promise函数进行一次封装,并实现与该特性一样的效果(那它应该算是Promise最佳实践了吧)?

未使用with resolver方式封装的代码组织,依然采用回调式编程方法

const promise = new Promise((resolve, reject) => {
  asyncRequest(config, response => {
    const buffer = [];
    response.on('data', data => buffer.push(data));
    response.on('end', () => resolve(buffer));
    response.on('error', reason => reject(reason));
  });
});

利用引用将res和rej映射到外部,这也算的上一种闭包了

let resolve, reject;
const promise = new Promise((res, rej) => {
  resolve = res;
  reject = rej;
});

asyncRequest(config, response => {
  const buffer = [];
  response.on('callback-request', id => {
    promise.then(data => callback(id, data));
  });
  response.on('data', data => buffer.push(data));
  response.on('end', () => resolve(buffer));
  response.on('error', reason => reject(reason));
});

我们可见,上述代码的组织逻辑从原先的回调嵌套转化为了顺序执行,同时保留了promise的能力

针对resolve和reject会传递数据到下层调用,所以出现了以下的代码调用书写方式

let resolve = () => { };
let reject = () => { };

function request(type, message) {
  if (socket) {
    const promise = new Promise((res, rej) => {
      resolve = res;
      reject = rej;
    });
    socket.emit(type, message);
    return promise;
  }

  return Promise.reject(new Error('Socket unavailable'));
}

socket.on('response', response => {
  if (response.status === 200) {
    resolve(response);
  }
  else {
    reject(new Error(response));
  }
});

socket.on('error', err => {
  reject(err);
});

这个方式看起来并不复杂,仅仅是在定义调用的时候向回调函数设置参数

接下来就是withResolvers函数真正实现的调用方式了

const { promise, resolve, reject } = Promise.withResolvers();

根据TC39的提案仓库,我们可以找到实现该特性的polyfills代码,不难看出该代码的组织形式就是将上述的几个逻辑封装,并利用闭包向外传递函数的引用.

export function withResolvers() {
	if (!this) throw new TypeError("Promise.withResolvers called on non-object")
	const out = {}
	out.promise = new this((resolve_, reject_) => {
		out.resolve = resolve_
		out.reject = reject_
	})
	return out
}

扩展: 如果是ts我们如何定义呢,我貌似没有找到实现该仓库的types 猜测使用方式与Promise的使用方式类似,需要定义成功和失败的返回类型

interface Resolve {}
interface Reject {}

// promise:Promise<Resolve,Reject>
// resolve: (Resolve)=>void
// reject: (Reject)=>void
const { promise, resolve, reject } = Promise.withResolvers<Resolver,Reject>();

// 回调处理
promise.then((res)=>{}) // res: Resolve
promise.catch((rej)=>{})// rej: Reject

////////////////////////or/////////////////////////////
// async 处理
async test(){
    const data = await promise // data: Resolve
}

我们在vue/core项目中的componentAsync.ts文件中可以看到 convertLegacyAsyncComponent 方法使用了类似的书写方式,如下图所示.


export function convertLegacyAsyncComponent(comp: LegacyAsyncComponent) {
  ...
  let resolve: (res: LegacyAsyncReturnValue) => void
  let reject: (reason?: any) => void
  
  const fallbackPromise = new Promise<Component>((r, rj) => {
    ;(resolve = r), (reject = rj)
  })

  const res = comp(resolve!, reject!)
  ...
}