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

ES6 系列之 Promise 及异步处理的几种方式 #73

Open
yangtao2o opened this issue Apr 6, 2020 · 0 comments
Open

ES6 系列之 Promise 及异步处理的几种方式 #73

yangtao2o opened this issue Apr 6, 2020 · 0 comments

Comments

@yangtao2o
Copy link
Owner

yangtao2o commented Apr 6, 2020

Promise 定义

总结一下回调函数的情况:

  • 回调函数执行多次
  • 回调函数没有执行
  • 回调函数有时同步执行有时异步执行

对于这些情况,可能都要在回调函数中做些处理,并且每次执行回调函数的时候都要做些处理,这就带来了很多重复的代码。

回调地狱的其他问题:

  • 难以复用
  • 堆栈信息被断开
  • 借助外层变量

Promise 使得以上绝大部分的问题都得到了解决。

  1. 嵌套问题
request(url)
  .then(function(result) {
    return writeFileAsynv("1.txt", result);
  })
  .then(function(result) {
    return request(url2);
  })
  .catch(function(e) {
    handleError(e);
  });
  1. 控制反转再反转

使用第三方回调 API 的时候,可能会遇到如下问题:

  1. 回调函数执行多次
  2. 回调函数没有执行
  3. 回调函数有时同步执行有时异步执行

对于第一个问题,Promise 只能 resolve 一次,剩下的调用都会被忽略。

对于第二个问题,我们可以使用 Promise.race 函数来解决。

对于第三个问题,即使 promise 对象立刻进入 resolved 状态,即同步调用 resolve 函数,then 函数中指定的方法依然是异步进行的。

PromiseA+ 规范也有明确的规定:

实践中要确保 onFulfilled 和 onRejected 方法异步执行,且应该在 then 方法被调用的那一轮事件循环之后的新执行栈中执行。

Promise 的局限性

  1. 错误被吃掉

其实这并不是 Promise 独有的局限性,try catch 也是这样,同样会捕获一个异常并简单的吃掉错误。

而正是因为错误被吃掉,Promise 链中的错误很容易被忽略掉,这也是为什么会一般推荐在 Promise 链的最后添加一个 catch 函数,因为对于一个没有错误处理函数的 Promise 链,任何错误都会在链中被传播下去,直到你注册了错误处理函数。

  1. 单一值

Promise 只能有一个完成值或一个拒绝原因,当需要传递多个值时,构造成一个对象或数组,然后再传递,then 中获得这个值后,又会进行取值赋值的操作。使用 ES6 的解构赋值:

Promise.all([Promise.resolve(1), Promise.resolve(2)]).then(([x, y]) => {
  console.log(x, y); // 1 2
});
  1. 无法取消

Promise 一旦新建它就会立即执行,无法中途取消。

  1. 无法得知 pending 状态

当处于 pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

异步处理的几种方式

题目:红灯三秒亮一次,绿灯一秒亮一次,黄灯两秒亮一次,不断交替循环

先定义下红绿灯:

function red() {
  console.log("red");
}
function green() {
  console.log("green");
}
function yellow() {
  console.log("yellow");
}

异步编程的语法目标,就是怎样让它更像同步编程,有以下几种:

  • 回调函数实现
  • 事件监听 event
  • 发布订阅 Publish/Subscribe
  • Promise 和 Generator
  • Async/await

一、回调函数

这是最常见的一种方式,把函数作为参数送入,然后回调。

第一版:简单明了

function step() {
  console.log("wait for about 3 seconds...");
  setTimeout(() => {
    red();
    setTimeout(() => {
      green();
      setTimeout(() => {
        yellow();
        step();
      }, 2000);
    }, 1000);
  }, 3000);
}

step();

第二版:封装定时器

var light = (timmer, cb) => {
  setTimeout(() => {
    cb();
  }, timmer);
};

function step(cb) {
  light(3000, () => {
    red();
    light(1000, () => {
      green();
      light(2000, () => {
        yellow();
        step();
      });
    });
  });
  typeof cb === "function" && cb();
}

step(() => console.log("wait for about 3 seconds..."));

二、事件监听

采用事件驱动模式。任务的执行不取决于代码的顺序,而取决于某个事件是否发生。

第一版:监听一个事件,然后触发这个事件,并且执行事件里的回调函数

// 引入 Node events 模块
const events = require("events");
const emitter = new events.EventEmitter();

// 监听
emitter.on("lightEvent", str => console.log(str));

// 触发
emitter.emit("lightEvent", "red");
emitter.emit("lightEvent", "green");
emitter.emit("lightEvent", "yellow");

// 输出
// red
// green
// yellow

第二版:加个顺序执行

// 引入 Node events 模块
const events = require("events");
const emitter = new events.EventEmitter();

const lightHandler = (timmer, cb) => {
  setTimeout(() => {
    cb();
  }, timmer);
};

// 监听
emitter.on("lightEvent", str => console.log(str));

// 触发
function step() {
  lightHandler(3000, () => {
    emitter.emit("lightEvent", "red");
    lightHandler(1000, () => {
      emitter.emit("lightEvent", "green");
      lightHandler(2000, () => {
        emitter.emit("lightEvent", "yellow");
        step();
      });
    });
  });
}

step();

依旧是回调执行,我们继续远征吧。

三、发布/订阅

"事件",完全可以理解成"信号"。

我们假定,存在一个"信号中心",某个任务执行完成,就向信号中心"发布"(publish)一个信号,其他任务可以向信号中心"订阅"(subscribe)这个信号,从而知道什么时候自己可以开始执行。这就叫做"发布/订阅模式"(publish-subscribe pattern),又称"观察者模式"(observer pattern)。 - 阮一峰

订阅者(Subscriber)把自己想订阅的事件注册(Subscribe)到调度中心(Event Channel),当发布者(Publisher)发布该事件(Publish Event)到调度中心,也就是该事件触发时,由调度中心统一调度(Fire Event)订阅者注册到调度中心的处理代码。

第一版:

const publisher = {
  // 缓存列表
  lists: {},
  // 订阅
  subscribe: function(event, handler) {
    (this.lists[event] || (this.lists[event] = [])).push(handler);
  },
  // 发布
  publish: function() {
    const event = [].shift.call(arguments);
    const events = this.lists[event];

    if (!events || events.length === 0) {
      return false;
    }

    events.forEach(item => {
      item.apply(this, arguments);
    });
  }
};

// 订阅
publisher.subscribe("lightEvent", red);
publisher.subscribe("lightEvent", green);
publisher.subscribe("lightEvent", yellow);

// 发布
publisher.publish("lightEvent");

第二版:

const publisher = {
  // 缓存列表
  lists: {},
  // 订阅
  subscribe: function(event, handler) {
    (this.lists[event] || (this.lists[event] = [])).push(handler);
  },
  // 取消订阅
  unsubscribe: function(event, handler) {
    const events = this.lists[event];
    if (!events) {
      return false;
    }
    if (!handler) {
      events && (events.length = 0);
    } else {
      events.forEach((item, i) => {
        if (item === handler) {
          events.splice(i, 1);
        }
      });
    }
  },
  // 发布
  publish: function() {
    const event = [].shift.call(arguments);
    const events = this.lists[event];

    if (!events || events.length === 0) {
      return false;
    }

    events.forEach(item => {
      item.apply(this, arguments);
    });
  }
};

const lightHandler = (timmer, cb) => {
  setTimeout(() => {
    cb();
  }, timmer);
};

const colorHandler = color => console.log(color);

// 订阅
publisher.subscribe("redEvent", colorHandler);
publisher.subscribe("greenEvent", colorHandler);
publisher.subscribe("yellowEvent", colorHandler);

function step() {
  lightHandler(3000, () => {
    publisher.publish("redEvent", "red");
    lightHandler(1000, () => {
      publisher.publish("greenEvent", "green");
      lightHandler(2000, () => {
        publisher.publish("yellowEvent", "yellow");
        step();
      });
    });
  });
}

step();

三、Promise

直接上代码:

var light = (timmer, cb) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      cb();
      resolve();
    }, timmer);
  });
};

var step = () => {
  Promise.resolve()
    .then(() => {
      return light(3000, red);
    })
    .then(() => {
      return light(1000, green);
    })
    .then(() => {
      return light(2000, yellow);
    })
    .then(() => {
      step();
    })
    .catch(err => console.log(err));
};

step();

四、Generator

Promise 的写法减少了好多回调,但是仍有回调的存在,这次尝试使用 Generator,看是否能够避免回调。

const light = (timmer, cb) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      cb();
      resolve();
    }, timmer);
  });
};

function* gen() {
  yield light(3000, red);
  yield light(1000, green);
  yield light(3000, yellow);
}

const iterator = gen();

const step = (gen, iterator) => {
  const s = iterator.next();
  // 返回 { value: Promise { <pending> }, done: false }
  if (s.done) {
    step(gen, gen());
  } else {
    // value 返回 Promise 对象
    s.value.then(() => {
      step(gen, iterator);
    });
  }
};

step(gen, iterator);

五、Async/await

有了 Generator 做铺垫,async/await 就比较容易理解了:

const light = (timmer, cb) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      cb();
      resolve();
    }, timmer);
  });
};

async function step() {
  await light(3000, red);
  await light(1000, green);
  await light(2000, yellow);
  step();
}

step();

同步写法,容易理解,和我们的线性思考方式一致,async/awaitES2017 的方案。

学习资料

@yangtao2o yangtao2o changed the title ES6 系列之我们来聊聊 Promise ES6 系列之 Promise 及异步处理的几种方式 Apr 7, 2020
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

1 participant