Từ khóa năng suất trong JavaScript là gì?


237

Tôi đã nghe nói về một từ khóa "suất" trong JavaScript, nhưng tôi thấy tài liệu rất kém về nó. Ai đó có thể giải thích cho tôi (hoặc giới thiệu một trang web giải thích) việc sử dụng nó và nó được sử dụng để làm gì không?


Anh ta có lẽ có nghĩa là 'Yield' byte.com/topic/python/answers/685510-yield-keyword-usage
ant

4
nó được giải thích trong MDN , nhưng tôi nghĩ rằng điều này chỉ hoạt động cho firefox, phải không? Làm thế nào là di động? Có cách nào để làm điều này trên Chrome hoặc node.js không? PD: xin lỗi, đó là Javascript v1.7 + , vì vậy đó là tài sản cần xem xét khi tìm kiếm hỗ trợ.
Trylks

1
@Trylks: Máy phát điện có sẵn trong Node kể từ v0.11.2
Janus Troelsen

@JanusTroelsen tuy nhiên, chỉ đứng sau một lá cờ. Chúng được hỗ trợ nguyên bản trong ioJS
Dan Pantry

Câu trả lời:


85

Các tài liệu MDN là khá tốt, IMO.

Hàm chứa từ khóa suất là một trình tạo. Khi bạn gọi nó, các tham số chính thức của nó bị ràng buộc với các đối số thực tế, nhưng cơ thể của nó không thực sự được đánh giá. Thay vào đó, một trình tạo vòng lặp được trả về. Mỗi lệnh gọi phương thức next () của trình tạo-iterator thực hiện một lần chuyển khác thông qua thuật toán lặp. Giá trị của mỗi bước là giá trị được chỉ định bởi từ khóa suất. Hãy nghĩ về năng suất như là phiên bản trả về của trình tạo-trình lặp, chỉ ra ranh giới giữa mỗi lần lặp của thuật toán. Mỗi lần bạn gọi next (), mã trình tạo lại tiếp tục từ câu lệnh theo sau sản lượng.


2
@NicolasBarbulesco có một ví dụ được đặt rất rõ ràng nếu bạn nhấp qua tài liệu MDN.
Matt Ball

@MattBall - một chức năng như javascript cho PI như thế này là đủ như sau: function * PI {PI = ((Math.SQRT8;) / 9801;); } - hoặc đã có một hàm được triển khai trong javascript để tính PI này chưa?
dschinn1001

4
Điểm trích dẫn MDN ở đây là gì? Tôi nghĩ mọi người đều có thể đọc nó trên MDN. Truy cập davidwalsh.name/promises để tìm hiểu thêm về chúng.
Ejaz Karim

20
Làm thế nào điều này nhận được ~ 80 upvote khi (a) nó là một bản sao của "tài liệu rất kém" khi người hỏi gọi nó và (b) nó nói không có gì hữu ích? Câu trả lời tốt hơn dưới đây.
www-0av-Com

4
nếu ai đó yêu cầu giải thích, chỉ cần sao chép dán tài liệu là hoàn toàn không đáng tin. Hỏi có nghĩa là bạn đã tìm kiếm trong tài liệu nhưng bạn không hiểu chúng.
Diego

205

Trả lời muộn, có lẽ mọi người đều biết về yieldbây giờ, nhưng một số tài liệu tốt hơn đã xuất hiện.

Điều chỉnh một ví dụ từ "Tương lai của Javascript: Trình tạo" của James Long cho tiêu chuẩn Harmony chính thức:

function * foo(x) {
    while (true) {
        x = x * 2;
        yield x;
    }
}

"Khi bạn gọi foo, bạn nhận lại một đối tượng Trình tạo có phương thức tiếp theo."

var g = foo(2);
g.next(); // -> 4
g.next(); // -> 8
g.next(); // -> 16

Vì vậy, yieldlà loại giống như return: bạn sẽ có được một cái gì đó trở lại. return xtrả về giá trị của x, nhưng yield xtrả về một hàm, cung cấp cho bạn một phương thức để lặp lại giá trị tiếp theo. Hữu ích nếu bạn có một thủ tục cần nhiều bộ nhớ mà bạn có thể muốn làm gián đoạn trong quá trình lặp.


13
Hữu ích, nhưng tôi đoán bạn function* foo(x){ở đó
Rana Deep

9
@RanaDeep: Cú pháp hàm được mở rộng để thêm mã thông báo tùy chọn * . Bạn có cần hay không tùy thuộc vào loại tương lai bạn sẽ trở lại. Chi tiết rất dài: GvR giải thích nó cho việc triển khai Python , theo đó việc triển khai Javascript được mô hình hóa. Sử dụng function *sẽ luôn luôn đúng, mặc dù trong một số trường hợp hơi quá phí so functionvới yield.
giám mục

1
@ Ajedi32 Đúng, bạn đúng. Harmony đã chuẩn hóa mối tương quan giữa function *yieldvà thêm lỗi được trích dẫn ("Lỗi sớm được đưa ra nếu biểu thức sản lượng hoặc sản lượng * xảy ra trong hàm không tạo"). Nhưng, việc triển khai Javascript 1.7 ban đầu trong Firefox không yêu cầu* . Cập nhật câu trả lời phù hợp. Cảm ơn!
giám mục

3
@MuhammadUmer Js cuối cùng đã trở thành ngôn ngữ bạn có thể sử dụng. Nó được gọi là tiến hóa.
Lukas Liesis

1
ví dụ là hữu ích, nhưng ... chức năng * là gì?
Diego

65

Nó thực sự đơn giản, đây là cách nó hoạt động

  • yieldtừ khóa chỉ đơn giản là giúp tạm dừngtiếp tục một chức năng bất cứ lúc nào không đồng bộ .
  • Ngoài ra, nó giúp trả về giá trị từ một hàm tạo .

Thực hiện chức năng tạo đơn giản này :

function* process() {
    console.log('Start process 1');
    console.log('Pause process2 until call next()');

    yield;

    console.log('Resumed process2');
    console.log('Pause process3 until call next()');

    let parms = yield {age: 12};
    console.log("Passed by final process next(90): " + parms);

    console.log('Resumed process3');
    console.log('End of the process function');
}

let _ process = process ();

Cho đến khi bạn gọi _ process.next ()sẽ không thực thi 2 dòngđầu tiên , thì sản lượng đầu tiên sẽ tạm dừng chức năng. Để tiếp tục chức năng cho đến điểm tạm dừng tiếp theo ( từ khóa suất ), bạn cần gọi _ process.next () .

Bạn có thể nghĩ nhiều sản lượngđiểm dừng trong trình gỡ lỗi javascript trong một chức năng. Cho đến khi bạn nói để điều hướng điểm dừng tiếp theo, nó sẽ không thực thi khối mã. ( Lưu ý : không chặn toàn bộ ứng dụng)

Nhưng trong khi năng suất Thực hiện tạm dừng này và tiếp tục hành vi đó có thể trở lại một số kết quả cũng {value: any, done: boolean} theo chức năng trước đây, chúng tôi đã không phát ra bất kỳ giá trị. Nếu chúng ta khám phá đầu ra trước đó, nó sẽ hiển thị tương tự { value: undefined, done: false } với giá trị không xác định .

Hãy đào sâu vào từ khóa năng suất. Tùy chọn bạn có thể thêm biểu thức và đặt gán một giá trị tùy chọn mặc định . (Cú pháp tài liệu chính thức)

[rv] = yield [expression];

biểu thức : Giá trị trả về từ hàm tạo

yield any;
yield {age: 12};

rv : Trả về giá trị tùy chọn được truyền cho phương thức next () của trình tạo

Đơn giản là bạn có thể truyền tham số cho hàm process () với cơ chế này, để thực thi các phần năng suất khác nhau.

let val = yield 99; 

_process.next(10);
now the val will be 10 

Thử ngay bây giờ

Tập quán

  • Đánh giá lười biếng
  • Trình tự vô hạn
  • Luồng điều khiển không đồng bộ

Người giới thiệu:


54

Đơn giản hóa / xây dựng dựa trên câu trả lời của Nick Sotiros (mà tôi nghĩ là tuyệt vời), tôi nghĩ tốt nhất là mô tả cách người ta sẽ bắt đầu viết mã yield.

Theo tôi, ưu điểm lớn nhất của việc sử dụng yieldlà nó sẽ loại bỏ tất cả các vấn đề gọi lại lồng nhau mà chúng ta thấy trong mã. Thật khó để thấy làm thế nào lúc đầu, đó là lý do tại sao tôi quyết định viết câu trả lời này (cho bản thân tôi và hy vọng những người khác!)

Cách thức thực hiện là bằng cách đưa ra ý tưởng về đồng quy, đây là chức năng có thể tự nguyện dừng / tạm dừng cho đến khi nhận được những gì nó cần. Trong javascript, điều này được ký hiệu là function*. Chỉ các function*chức năng có thể sử dụng yield.

Đây là một số javascript điển hình:

loadFromDB('query', function (err, result) {
  // Do something with the result or handle the error
})

Điều này thật rắc rối bởi vì bây giờ tất cả mã của bạn (rõ ràng cần phải chờ loadFromDBcuộc gọi này ) cần phải nằm trong cuộc gọi lại trông xấu xí này. Điều này là xấu vì một vài lý do ...

  • Tất cả mã của bạn được thụt vào một cấp trong
  • Bạn có kết thúc này })mà bạn cần theo dõi ở khắp mọi nơi
  • Tất cả các function (err, result)biệt ngữ thêm này
  • Không chính xác rõ ràng rằng bạn đang làm điều này để gán giá trị cho result

Mặt khác, với yield, tất cả những điều này có thể được thực hiện trong một dòng với sự trợ giúp của khung công tác hợp tác tốt đẹp.

function* main() {
  var result = yield loadFromDB('query')
}

Và vì vậy, bây giờ chức năng chính của bạn sẽ mang lại khi cần thiết khi nó cần chờ các biến và những thứ được tải. Nhưng bây giờ, để chạy cái này, bạn cần gọi một hàm bình thường (hàm không coroutine). Một khung công tác đồng thường đơn giản có thể khắc phục vấn đề này để tất cả những gì bạn phải làm là chạy nó:

start(main())

Và bắt đầu được xác định (từ câu trả lời của Nick Sotiro)

function start(routine, data) {
    result = routine.next(data);
    if(!result.done) {
        result.value(function(err, data) {
            if(err) routine.throw(err); // continue next iteration of routine with an exception
            else start(routine, data);  // continue next iteration of routine normally
        });
    }
}

Và bây giờ, bạn có thể có mã đẹp dễ đọc hơn nhiều, dễ xóa và không cần phải sử dụng thụt lề, hàm, v.v.

Một quan sát thú vị là trong ví dụ này, yieldthực sự chỉ là một từ khóa bạn có thể đặt trước một hàm với một cuộc gọi lại.

function* main() {
  console.log(yield function(cb) { cb(null, "Hello World") })
}

Sẽ in "Xin chào thế giới". Vì vậy, bạn thực sự có thể biến bất kỳ hàm gọi lại nào thành sử dụng bằng yieldcách chỉ cần tạo cùng một chữ ký hàm (không có cb) và trả về function (cb) {}, như vậy:

function yieldAsyncFunc(arg1, arg2) {
  return function (cb) {
    realAsyncFunc(arg1, arg2, cb)
  }
}

Hy vọng với kiến ​​thức này bạn có thể viết mã sạch hơn, dễ đọc hơn và dễ xóa !


a function*chỉ là một chức năng thông thường mà không có năng suất?
Abdul

Tôi nghĩ bạn có nghĩa function *là một chức năng có chứa năng suất. Đó là một chức năng đặc biệt gọi là máy phát điện.
Rời

7
Đối với những người đã sử dụng yieldở mọi nơi, tôi chắc chắn rằng điều này có ý nghĩa hơn so với các cuộc gọi lại, nhưng tôi không thấy cách này dễ đọc hơn cuộc gọi lại.
palswim

bài viết đó thật khó hiểu
Martian2049

18

Để đưa ra một câu trả lời hoàn chỉnh: yieldđang hoạt động tương tự return, nhưng trong một máy phát điện.

Đối với ví dụ thường được đưa ra, điều này hoạt động như sau:

function *squareGen(x) {
    var i;
    for (i = 0; i < x; i++) {
        yield i*i;
    }
}

var gen = squareGen(3);

console.log(gen.next().value); // prints 0
console.log(gen.next().value); // prints 1
console.log(gen.next().value); // prints 4

Nhưng cũng có một mục đích thứ hai của từ khóa năng suất. Nó có thể được sử dụng để gửi các giá trị đến trình tạo.

Để làm rõ, một ví dụ nhỏ:

function *sendStuff() {
    y = yield (0);
    yield y*y;
}

var gen = sendStuff();

console.log(gen.next().value); // prints 0
console.log(gen.next(2).value); // prints 4

Điều này hoạt động, như giá trị 2được gán cho y, bằng cách gửi nó đến trình tạo, sau khi nó dừng ở mức sản lượng đầu tiên (trả về 0).

Điều này cho phép chúng tôi để một số công cụ thực sự thú vị. (tra cứu coroutine)


16

Nó được sử dụng cho các trình tạo vòng lặp. Về cơ bản, nó cho phép bạn tạo một chuỗi (có khả năng vô hạn) bằng cách sử dụng mã thủ tục. Xem tài liệu của Mozilla .


6

yield cũng có thể được sử dụng để loại bỏ địa ngục gọi lại, với khung coroutine.

function start(routine, data) {
    result = routine.next(data);
    if(!result.done) {
        result.value(function(err, data) {
            if(err) routine.throw(err); // continue next iteration of routine with an exception
            else start(routine, data);  // continue next iteration of routine normally
        });
    }
}

// with nodejs as 'node --harmony'
fs = require('fs');
function read(path) {
    return function(callback) { fs.readFile(path, {encoding:'utf8'}, callback); };
}

function* routine() {
    text = yield read('/path/to/some/file.txt');
    console.log(text);
}

// with mdn javascript 1.7
http.get = function(url) {
    return function(callback) { 
        // make xhr request object, 
        // use callback(null, resonseText) on status 200,
        // or callback(responseText) on status 500
    };
};

function* routine() {
    text = yield http.get('/path/to/some/file.txt');
    console.log(text);
}

// invoked as.., on both mdn and nodejs

start(routine());

4

Trình tạo chuỗi Fibonacci sử dụng từ khóa năng suất.

function* fibbonaci(){
    var a = -1, b = 1, c;
    while(1){
        c = a + b;
        a = b;
        b = c;
        yield c;
    }   
}

var fibonacciGenerator = fibbonaci();
fibonacciGenerator.next().value; // 0 
fibonacciGenerator.next().value; // 1
fibonacciGenerator.next().value; // 1
fibonacciGenerator.next().value; // 2 

4

Yeild từ khóa trong hàm javaScript làm cho nó tạo ra,

trình tạo trong javaScript là gì?

Trình tạo là một hàm tạo ra một chuỗi kết quả thay vì một giá trị duy nhất, tức là bạn tạo ra một chuỗi các giá trị

Có nghĩa là các trình tạo giúp chúng ta làm việc không đồng bộ với các trình vòng lặp trợ giúp, Oh bây giờ các trình vòng lặp hack là gì? có thật không?

Lặp đi lặp lại có nghĩa là thông qua đó chúng ta có thể truy cập từng mục một

từ nơi iterator giúp chúng ta truy cập từng mục một? nó giúp chúng ta truy cập các mục thông qua các chức năng của trình tạo

các hàm tạo là những hàm mà chúng ta sử dụng yeildtừ khóa, từ khóa năng suất giúp chúng ta tạm dừng và tiếp tục thực thi hàm

đây là ví dụ nhanh

function *getMeDrink() {

    let question1 = yield 'soda or beer' // execution will pause here because of yield

 if (question1 == 'soda') {

            return 'here you get your soda'

    }

    if (question1 == 'beer') {

        let question2 = yield 'Whats your age' // execution will pause here because of yield

        if (question2 > 18) {

            return "ok you are eligible for it"

        } else {

            return 'Shhhh!!!!'

        }
    }
}


let _getMeDrink = getMeDrink() // initialize it

_getMeDrink.next().value  // "soda or beer"

_getMeDrink.next('beer').value  // "Whats your age"

_getMeDrink.next('20').value  // "ok you are eligible for it"

_getMeDrink.next().value // undefined

hãy để tôi giải thích những gì đang xảy ra

bạn nhận thấy việc thực thi đang bị tạm dừng tại mỗi yeildtừ khóa và chúng tôi có thể truy cập trước yieldvới sự trợ giúp của iterator.next()

điều này lặp lại cho tất cả các yieldtừ khóa một lần và sau đó trả về không xác định khi không còn yieldtừ khóa nào trong những từ đơn giản mà bạn có thể nóiyield từ khóa là điểm dừng trong đó chức năng mỗi lần tạm dừng và chỉ tiếp tục khi gọi nó bằng cách sử dụng iterator

đối với trường hợp của chúng tôi: _getMeDrink.next()đây là ví dụ về trình vòng lặp giúp chúng tôi truy cập từng điểm dừng trong chức năng

Ví dụ về Máy phát điện: async/await

nếu bạn thấy việc thực hiện async/await bạn sẽ thấy generator functions & promisesđược sử dụng để thực hiện async/awaitcông việc

xin vui lòng chỉ ra bất kỳ đề nghị được hoan nghênh


3

Sự phụ thuộc giữa các cuộc gọi javascript async.

Một ví dụ khác về cách sản lượng có thể được sử dụng.

function request(url) {
  axios.get(url).then((reponse) => {
    it.next(response);
  })
}

function* main() {
  const result1 = yield request('http://some.api.com' );
  const result2 = yield request('http://some.otherapi?id=' + result1.id );
  console.log('Your response is: ' + result2.value);
}

var it = main();
it.next()


0

Trước khi bạn tìm hiểu về năng suất, bạn cần biết về máy phát điện. Máy phát điện được tạo bằng function*cú pháp. Các hàm tạo không thực thi mã mà thay vào đó trả về một kiểu trình vòng lặp được gọi là trình tạo. Khi một giá trị được đưa ra bằng nextphương thức, hàm tạo sẽ tiếp tục thực thi cho đến khi gặp một từ khóa năng suất. Việc sử dụng yieldmang lại cho bạn một đối tượng chứa hai giá trị, một là giá trị và đối tượng kia được thực hiện (boolean). Giá trị có thể là một mảng, đối tượng, vv


0

Một ví dụ đơn giản:

const strArr = ["red", "green", "blue", "black"];

const strGen = function*() {
    for(let str of strArr) {
        yield str;
    }
};

let gen = strGen();

for (let i = 0; i < 5; i++) {
    console.log(gen.next())
}

//prints: {value: "red", done: false} -> 5 times with different colors, if you try it again as below:

console.log(gen.next());

//prints: {value: undefined, done: true}

0

Tôi cũng đang cố gắng để hiểu từ khóa năng suất. Dựa trên hiểu biết hiện tại của tôi, trong trình tạo, từ khóa năng suất hoạt động giống như bộ chuyển đổi ngữ cảnh CPU. Khi câu lệnh năng suất được chạy, tất cả các trạng thái (ví dụ: biến cục bộ) được lưu.

Bên cạnh đó, một đối tượng kết quả trực tiếp sẽ được trả về cho người gọi, như {value: 0, xong: false}. Người gọi có thể sử dụng đối tượng kết quả này để quyết định xem có nên 'đánh thức' lại trình tạo hay không bằng cách gọi next () (gọi next () là để lặp lại quá trình thực thi).

Một điều quan trọng khác là nó có thể đặt giá trị cho biến cục bộ. Giá trị này có thể được chuyển qua bởi người gọi 'next ()' khi 'đánh thức' máy phát. ví dụ: it.next ('valueToPass'), như thế này: "resultValue = ieldQueryQuery (1);" Giống như khi đánh thức một lần thực hiện tiếp theo, người gọi có thể đưa một số kết quả đang chạy vào thực thi (đưa nó vào biến cục bộ). Vì vậy, đối với việc thực hiện này, có hai loại trạng thái:

  1. bối cảnh đã lưu trong lần thực hiện cuối cùng.

  2. Các giá trị được chèn bởi trình kích hoạt này.

Vì vậy, với tính năng này, trình tạo có thể sắp xếp nhiều hoạt động không đồng bộ. Kết quả của truy vấn không đồng bộ đầu tiên sẽ được chuyển sang truy vấn thứ hai bằng cách đặt biến cục bộ (resultValue trong ví dụ trên). Truy vấn async thứ hai chỉ có thể được kích hoạt bởi phản hồi của truy vấn async đầu tiên. Sau đó, truy vấn async thứ hai có thể kiểm tra giá trị biến cục bộ để quyết định các bước tiếp theo vì biến cục bộ là giá trị được chèn từ phản hồi của truy vấn đầu tiên.

Những khó khăn của truy vấn không đồng bộ là:

  1. gọi lại địa ngục

  2. mất bối cảnh trừ khi chuyển chúng dưới dạng tham số trong cuộc gọi lại.

năng suất và máy phát điện có thể giúp cả hai.

Không có năng suất và trình tạo, để sắp xếp nhiều truy vấn không đồng bộ yêu cầu gọi lại lồng nhau với các tham số là bối cảnh không dễ đọc và duy trì.

Dưới đây là một ví dụ truy vấn async được xâu chuỗi đang chạy với nodejs:

const axios = require('axios');

function slowQuery(url) {        
    axios.get(url)
    .then(function (response) {
            it.next(1);
    })
    .catch(function (error) {
            it.next(0);
    })
}

function* myGen(i=0) {
    let queryResult = 0;

    console.log("query1", queryResult);
    queryResult = yield slowQuery('https://google.com');


    if(queryResult == 1) {
        console.log("query2", queryResult);
        //change it to the correct url and run again.
        queryResult = yield slowQuery('https://1111111111google.com');
    }

    if(queryResult == 1) {
        console.log("query3", queryResult);
        queryResult =  yield slowQuery('https://google.com');
    } else {
        console.log("query4", queryResult);
        queryResult = yield slowQuery('https://google.com');
    }
}

console.log("+++++++++++start+++++++++++");
let it = myGen();
let result = it.next();
console.log("+++++++++++end+++++++++++");

Dưới đây là kết quả chạy:

+++++++++++ bắt đầu +++++++++++

truy vấn1 0

+++++++++++ kết thúc +++++++++++

truy vấn2 1

truy vấn4 0

Dưới đây mẫu trạng thái có thể làm điều tương tự cho ví dụ trên:

const axios = require('axios');

function slowQuery(url) {
    axios.get(url)
        .then(function (response) {
            sm.next(1);
        })
        .catch(function (error) {
            sm.next(0);
        })
}

class StateMachine {
        constructor () {
            this.handler = handlerA;
            this.next = (result = 1) => this.handler(this, result);
        }
}

const handlerA = (sm, result) => {
                                    const queryResult = result; //similar with generator injection
                                    console.log("query1", queryResult);
                                    slowQuery('https://google.com');
                                    sm.handler = handlerB; //similar with yield;
                                };

const handlerB = (sm, result) => {
                                    const queryResult = result; //similar with generator injection
                                    if(queryResult == 1) {
                                        console.log("query2", queryResult);
                                        slowQuery('https://1111111111google.com');
                                    }
                                    sm.handler = handlerC; //similar with yield;
                                };

const handlerC = (sm, result) => {
                                    const queryResult = result; //similar with generator injection;
                                    if (result == 1 ) {
                                        console.log("query3", queryResult);
                                        slowQuery('https://google.com');
                                    } else {
                                        console.log("query4", queryResult);
                                        slowQuery('https://google.com');
                                    }
                                    sm.handler = handlerEnd; //similar with yield;
                                };

const handlerEnd = (sm, result) => {};

console.log("+++++++++++start+++++++++++");
const sm = new StateMachine();
sm.next();
console.log("+++++++++++end+++++++++++");

Sau đây là kết quả hoạt động:

+++++++++++ bắt đầu +++++++++++

truy vấn1 0

+++++++++++ kết thúc +++++++++++

truy vấn2 1

truy vấn4 0


0

đừng quên cú pháp 'x của trình tạo' rất hữu ích để lặp qua trình tạo. Không cần sử dụng hàm next ().

function* square(x){
    for(i=0;i<100;i++){
        x = x * 2;
        yield x;        
    }   
}

var gen = square(2);
for(x of gen){
   console.log(x);
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.