Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

阿里&字节:手写 async/await 的实现 #56

Open
sisterAn opened this issue May 28, 2020 · 4 comments
Open

阿里&字节:手写 async/await 的实现 #56

sisterAn opened this issue May 28, 2020 · 4 comments

Comments

@sisterAn
Copy link
Owner

sisterAn commented May 28, 2020

Async是如何被 JavaScript 实现的

await 内部实现了 generator,其实 await 就是 generator 加上 Promise的语法糖,且内部实现了自动执行 generator。如果你熟悉 co 的话,其实自己就可以实现这样的语法糖。

/**
 * async/await 实现
 * @param {*} generatorFunc 
 */
function asyncToGenerator(generatorFunc) {
  // 返回的是一个新的函数
  return function(...args) {
    // 先调用generator函数 生成迭代器
    // 对应 var gen = testG()
    const gen = generatorFunc.apply(this, args)

    // 返回一个promise 因为外部是用.then的方式 或者await的方式去使用这个函数的返回值的
    // var test = asyncToGenerator(testG)
    // test().then(res => console.log(res))
    return new Promise((resolve, reject) => {
    
      // 内部定义一个step函数 用来一步一步的跨过yield的阻碍
      // key有next和throw两种取值,分别对应了gen的next和throw方法
      // arg参数则是用来把promise resolve出来的值交给下一个yield
      function step(key, arg) {
        let genResult
        
        // 这个方法需要包裹在try catch中
        // 如果报错了 就把promise给reject掉 外部通过.catch可以获取到错误
        try {
          genResult = gen[key](arg)
        } catch (error) {
          return reject(error)
        }

        // gen.next() 得到的结果是一个 { value, done } 的结构
        const { value, done } = genResult

        if (done) {
          // 如果已经完成了 就直接resolve这个promise
          // 这个done是在最后一次调用next后才会为true
          // 以本文的例子来说 此时的结果是 { done: true, value: 'success' }
          // 这个value也就是generator函数最后的返回值
          return resolve(value)
        } else {
          // 除了最后结束的时候外,每次调用gen.next()
          // 其实是返回 { value: Promise, done: false } 的结构,
          // 这里要注意的是Promise.resolve可以接受一个promise为参数
          // 并且这个promise参数被resolve的时候,这个then才会被调用
          return Promise.resolve(
            // 这个value对应的是yield后面的promise
            value
          ).then(
            // value这个promise被resove的时候,就会执行next
            // 并且只要done不是true的时候 就会递归的往下解开promise
            // 对应gen.next().value.then(value => {
            //    gen.next(value).value.then(value2 => {
            //       gen.next() 
            //
            //      // 此时done为true了 整个promise被resolve了 
            //      // 最外部的test().then(res => console.log(res))的then就开始执行了
            //    })
            // })
            function onResolve(val) {
              step("next", val)
            },
            // 如果promise被reject了 就再次进入step函数
            // 不同的是,这次的try catch中调用的是gen.throw(err)
            // 那么自然就被catch到 然后把promise给reject掉啦
            function onReject(err) {
              step("throw", err)
            },
          )
        }
      }
      step("next")
    })
  }
}

var getData = () => new Promise(resolve => setTimeout(() => resolve('data'), 1000));
function* testG() {
  const data = yield getData();
  console.log('data: ', data);
  const data2 = yield getData();
  console.log('data2: ', data2);
  return 'success';
}

var gen = asyncToGenerator(testG);
gen().then(res => console.log(res));
@0undefined0
Copy link

0undefined0 commented May 29, 2020

之前看过一篇不错的,看看顺便默写下代码

function asyncToGen(genFunction) {
  return function (...args) {
    const gen = genFunction.apply(this, args);
    return new Promise((resolve, reject) => {
      function step(key, arg) {
        let genResult;
        try {
          genResult = gen[key](arg);
        } catch (err) {
          return reject(err);
        }
        const { value, done } = genResult;
        if (done) {
          return resolve(value);
        }
        return Promise.resolve(value).then(
          (val) => {
            step('next', val);
          },
          (err) => {
            step('throw', err);
          },
        );
      }
      step('next');
    });
  };
}
const getData = () => new Promise(resolve => setTimeout(() => resolve('data'), 1000));
function* testG() {
  const data = yield getData();
  console.log('data: ', data);
  const data2 = yield getData();
  console.log('data2: ', data2);
  return 'success';
}

const gen = asyncToGen(testG);
gen().then(res => console.log(res));

@tjwyz
Copy link

tjwyz commented Jul 16, 2020

本质是希望实现一个co函数

let delay = function (time, fnc) {
	setTimeout(() => {
		fnc(time);
	}, time);
}

let promisefy = (fn) => {
	return (...arg) => {
		return new Promise ((resolve, reject)=> {
			fn(...arg, (param)=>{
				resolve(param);
			})
		});
	}
}

let delayP = promisefy(delay);

const gen = function* () {
	const ret1 = yield delayP(1000);
	console.log(ret1);
	const ret2 = yield delayP(2000);
	console.log(ret2);
}

// 阴间写法
const g = gen();
g.next().value.then((res1)=>{
	g.next(res1).value.then((res2)=>{
		//
	});
})


// 正常写法
function co (generator) {
	return new Promise((resolve, reject)=>{
		const gen = generator();
		function next (...param) {
			let tmp = gen.next(...param);
			if (tmp.done) {
				resolve(tmp.value);
				return;
			}
			tmp.value.then((...ret)=>{
				next(...ret);
			})
		}
		next();
	})
}

co(gen).then((res)=>{
    console.log(res);
})

@xllpiupiu
Copy link

function fn(nums) {
    //返回一个Promise对象  因为async 就是返回Promise对象
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve(nums * 2)
        }, 1000)
    })
}
function* generator() {
    const num1 = yield fn(1)
    const num2 = yield fn(num1)
    const num3 = yield fn(num2)
    return num3
}
function generatorToAsync2(generator) {
    return function() {
        const gen = generator.apply(this,arguments)
        return new Promise((resolve,reject)=>{
            function _next(key,arg) {
                let res
                try {
                    res = gen[key](arg)
                    const {value,done} = res
                    if(done) {
                        return resolve(value)
                    } else {
                        return Promise.resolve(value).then(val=>_next('next',val),error=>_next('throw',error))
                    }
                } catch(error) {
                    return reject(error)
                }
            }
            _next('next')
        })
    }
}
const asyncFn2 = generatorToAsync2(generator)
asyncFn2().then(res=>console.log(res))

@waldonUB
Copy link

waldonUB commented Apr 21, 2022

基于前面的测试用例实现一个简单版的(代码可以复制运行)

  • 本质上是generator + promise
    1. generator的done为false时,递归
    2. generator的done为true时,递归终止,resolve结果
  • generator的value和done状态是迭代器协议的返回值
/**
 * async的实现
 * @author waldon
 * @param {*} generatorFn - 生成器函数
 */
function asyncWrapper(generatorFn) {
  const g = generatorFn()
  return new Promise((resolve, reject) => {
    function autoNext(g, nextVal) {
      const { value, done } = g.next(nextVal)
      if (!done) {
        value.then((res) => {
          autoNext(g, res)
        })
      } else {
        resolve(value)
      }
    }
    autoNext(g)
  })
}

// 测试

const getData = () => new Promise((resolve) => setTimeout(() => resolve('data'), 1000))

function* testG() {
  const data = yield getData()
  console.log('data: ', data)
  const data2 = yield getData()
  console.log('data2: ', data2)
  return 'success'
}

asyncWrapper(testG).then((res) => {
  console.log(res)
})

// 期望顺序输出 data data2 success

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants