Promise和Generator

作者: 越长越圆 | 来源:发表于2017-03-29 15:36 被阅读54次
    代码案例以及文章内容均来自阮一峰的ECAMScript6入门:http://es6.ruanyifeng.com/#docs/promise

    Promise对象

    Promise 是异步编程的一个解决方案,不同于回调,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。

    Promise对象有两个特点

    1. 对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:Pending,Resolved(已完成),Rejected(失败),只有异步操作的结果可以决定当前的状态,其他任何操作都无法改变
    2. 一旦状态改变,就不会在变,任何时候都可以得到这个结果,如果改变已经发生了,你在对Promise对象添加回调函数,也会立即得到这个结果
    //定义一个Promise
    var promise=new Promise(function(resolve,reject){
        //some code
        let value =200;
        if(true){
          resolve(value);
        }else{
           reject(200);
        }
    })
    
    使用
    promise.then(function(value){
       console.log(value);
    },function(error){
       console.log('error');
    })
    
    异步加载图片
    function loadImageAsync(url){
        return new Promise(function(resolve,reject){
            var image=new Image();
            image.onload=function(){
                resolve(image);
            };
            image.onerror=function(){
                reject(new Error('could not load image at '+url));
            };
            image.src=url;
        })
    }
    Promise包装了一个加载图片的异步操作,成功泽调用resolve,失败调用reject
    
    这里对xmlHttpRequest进行了封装,针对http请求返回一个promise对象,
    var getJSON=function(url){
        var promise=new Promise(function(resolve,reject){
            var client=new XMLHttpRequest();
            client.open('GET',url);
            client.onreadystatechange=handler;
            client.responseType='json';
            client.setRequestHeader('Accept','application/json');
            client.send();
    
            function handler(){
                if(this.readyState!==4){
                    return;
                }
                if(this.status===200){
                    resolve(this.response);
                }else{
                    reject(new Error(this.statusText));
                }
            };
        });
        return promise;
    };
    
    getJSON('/post.json').then(function(json){
        console.log('Content:'+json);
    },function(error){
        console.log('出差了',error)
    })
    
    Promise实例具有then方法和catch方法,所以推荐写法
    getJSON('/posts.json').then(function(posts){
    }).catch(function(error){
     console.log('发生错误',error);
    })
    
    简化后的写法
    p.then((val) => console.log('fulfilled:', val))
      .catch((err) => console.log('rejected', err)); 
    
    注意点

    如果Promise状态已经变成Resolved,在抛出错误无效

    var promise = new Promise(function(resolve, reject) {
      resolve('ok');
      throw new Error('test');
    });
    promise
      .then(function(value) { console.log(value) })
      .catch(function(error) { console.log(error) });
    
    这里抛出错误不会被捕获
    
    • Promise.all 可以将多个Promise实例,包装成一个新的Promise实例,只有等所有Promise实例的状态都变成fulfilled,或者其中一个变成rejected,才会调用后面的回调函数
    const databasePromise = connectDatabase();
    
    const booksPromise = databasePromise
      .then(findAllBooks);
    
    const userPromise = databasePromise
      .then(getCurrentUser);
    
    Promise.all([
      booksPromise,
      userPromise
    ])
    .then(([books, user]) => pickTopRecommentations(books, user));
    
    • Promise.race 同样是将多个Promise包装成一个新的Promise实例,但是如果其中一个实例状态先改变,就传递给p的回调函数
    const p = Promise.race([
      fetch('/resource-that-may-take-a-while'),
      new Promise(function (resolve, reject) {
        setTimeout(() => reject(new Error('request timeout')), 5000)
      })
    ]);
    p.then(response => console.log(response));
    p.catch(error => console.log(error));
    
    //如果5秒fetch没有反应,则抛出超时
    
    • Promise.resolve/reject 是将现有对象转换为Promise
    var p = Promise.resolve('Hello');
    
    p.then(function (s){
      console.log(s)
    });
    
    var p = Promise.reject('出错了');
    // 等同于
    var p = new Promise((resolve, reject) => reject('出错了'))
    
    p.then(null, function (s) {
      console.log(s)
    });
    
    • Promise.done/finally 两者最大的区别是finally接受个普通的回调函数作为参数
    asyncFunc()
      .then(f1)
      .catch(r1)
      .then(f2)
      .done();
    
    server.listen(0)
      .then(function () {
        // run test
      })
      .finally(server.stop);
    
    • Promise.try 在实际开发中,我们不知道函数f是同步还是异步函数,但是想用Promise去处理,因为这样我们就可以用then来处理
    const f = () => console.log('now');
    Promise.resolve().then(f);
    console.log('next');
    // next
    // now
    这是上面的一个缺点,如果f是同步,被Promise包装后变成异步执行,如果通过try来包装为所有操作提供统一的处理机制
    
    
    const f = () => console.log('now');
    Promise.try(f);
    console.log('next');
    
    // now
    // next
    

    Generator

    Generator 函数是ES6提供的一种异步编程解决方案,语法行为与传统函数完全不同,Generator函数是一个状态机,封装了多个内部状态,执行一个Generator函数会返回一个迭代器对象,可以依次便利Generator函数内部的每一个状态

    function* helloWorldGenerator() {
      yield 'hello';
      yield 'world';
      return 'ending';
    }
    
    var hw = helloWorldGenerator();
    
    hw.next()
    // { value: 'hello', done: false }
    
    hw.next()
    // { value: 'world', done: false }
    
    hw.next()
    // { value: 'ending', done: true }
    
    hw.next()
    // { value: undefined, done: true }
    
    其中value为 yield 标记的值,done 表示是否遍历结束,yield类似一个暂停标记,调用next方法会遍历下一个内部状态
    

    任意一个对象的Symbol.iterator方法,等于该对象的遍历器生成函数,调用该函数会返回该对象的一个遍历对象,由于Generator函数就是遍历器生成函数,因此可以把Generator赋值给对象的Symbol.iterator属性,从而使得该对象具有Iterator接口

    var myIterable = {};
    myIterable[Symbol.iterator] = function* () {
      yield 1;
      yield 2;
      yield 3;
    };
    
    [...myIterable] // [1, 2, 3]
    
    

    next方法的参数
    yield句本身没有返回值,或者总是返回underfined,next方法可以带一个参数,该参数作为上一个yield的返回值

    function* foo(x) {
      var y = 2 * (yield (x + 1));
      var z = yield (y / 3);
      return (x + y + z);
    }
    
    var a = foo(5);
    a.next() // Object{value:6, done:false}
    a.next() // Object{value:NaN, done:false}
    a.next() // Object{value:NaN, done:true}
    
    var b = foo(5);
    b.next() // { value:6, done:false }
    b.next(12) // { value:8, done:false }
    b.next(13) // { value:42, done:true }
    

    return 方法可以返回给定值,并且终结遍历Generator函数

    function* gen() {
      yield 1;
      yield 2;
      yield 3;
    }
    
    var g = gen();
    
    g.next()        // { value: 1, done: false }
    g.return('foo') // { value: "foo", done: true }
    g.next()        // { value: undefined, done: true }
    

    Generator函数调用另一个Generator函数

    function* foo() {
      yield 'a';
      yield 'b';
    }
    
    function* bar() {
      yield 'x';
      yield* foo();
      yield 'y';
    }
    
    

    Generator应用

    异步操作的同步化表达,可以将异步操作写在yield语句中,等调用next方法后再往后执行,这实际上等同于不需要写回调

    function* loadUI() {
      showLoadingScreen();
      yield loadUIDataAsynchronously();
      hideLoadingScreen();
    }
    var loader = loadUI();
    // 加载UI
    loader.next()
    
    // 卸载UI
    loader.next()
    
    ajax的异步操作
    function* main() {
      var result = yield request("http://some.url");
      var resp = JSON.parse(result);
        console.log(resp.value);
    }
    
    function request(url) {
      makeAjaxCall(url, function(response){
        it.next(response);
      });
    }
    
    var it = main();
    it.next();
    

    控制流管理

    Promise的写法
    Promise.resolve(step1)
      .then(step2)
      .then(step3)
      .then(step4)
      .then(function (value4) {
        // Do something with value4
      }, function (error) {
        // Handle any error from step1 through step4
      })
      .done();
    
    Generator的写法
    function* longRunningTask(value1) {
      try {
        var value2 = yield step1(value1);
        var value3 = yield step2(value2);
        var value4 = yield step3(value3);
        var value5 = yield step4(value4);
        // Do something with value4
      } catch (e) {
        // Handle any error from step1 through step4
      }
    }scheduler(longRunningTask(initialValue));
    
    function scheduler(task) {
      var taskObj = task.next(task.value);
      // 如果Generator函数未结束,就继续调用
      if (!taskObj.done) {
        task.value = taskObj.value
        scheduler(task);
      }
    }
    

    Async

    async 是Generator函数的语法糖,是的异步操作变得更加方便

    var fs = require('fs');
    
    var readFile = function (fileName) {
      return new Promise(function (resolve, reject) {
        fs.readFile(fileName, function(error, data) {
          if (error) reject(error);
          resolve(data);
        });
      });
    };
    
    var gen = function* () {
      var f1 = yield readFile('/etc/fstab');
      var f2 = yield readFile('/etc/shells');
      console.log(f1.toString());
      console.log(f2.toString());
    };
    
    写成asyc函数就变成
    var asyncReadFile = async function () {
      var f1 = await readFile('/etc/fstab');
      var f2 = await readFile('/etc/shells');
      console.log(f1.toString());
      console.log(f2.toString());
    };
    
    外观上看上去只是将*号替换成async,将yield 替换成await,但是 async对Generator函数的有了很大的改进
    1. 内置执行器:之前的操作都是xxx.next,而async函数自带执行器,他的执行与普通函数一模一样
    var result=asymcReadFile();
    
    1. 更好的语义
    2. 更广的适用性:async函数的await命令后面,可以是Promise对象和原始类型的值
    3. 返回值是Promise

    上述的代码案例均来之阮一峰的博客(更多代码案例):http://es6.ruanyifeng.com/#docs/promise

    书籍资料来自ECAMStrict6 入门第二版

    相关文章

      网友评论

        本文标题:Promise和Generator

        本文链接:https://www.haomeiwen.com/subject/dqzeottx.html